Merge pull request #520 from MaaAssistantArknights/dev

Dev
This commit is contained in:
MistEO
2022-05-18 01:10:59 +08:00
committed by GitHub
79 changed files with 3138 additions and 1191 deletions

View File

@@ -1,4 +1,4 @@
name: Deploy Docs
name: deploy-docs
on:
push:
@@ -9,7 +9,7 @@ on:
workflow_dispatch:
jobs:
build_and_deploy:
deploy-to-azure:
runs-on: ubuntu-latest
name: Build and Deploy Job
steps:

69
.github/workflows/dev-build.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
# ------------------------------------------------------------------------------
# <auto-generated>
#
# This code was generated.
#
# - To turn off auto-generation set:
#
# [GitHubActions (AutoGenerate = false)]
#
# - To trigger manual generation invoke:
#
# nuke --generate-configuration GitHubActions_dev-build --host GitHubActions
#
# </auto-generated>
# ------------------------------------------------------------------------------
name: dev-build
on:
push:
branches-ignore:
- master
paths:
- 'src/MeoAssistant/**'
- 'src/MeoAssistantBuilder/**'
- 'src/MeoAsstGui/**'
- '3rdparty/**'
- 'include/**'
- 'resource/**'
- MeoAssistantArknights.sln
pull_request:
branches:
- dev
paths:
- 'src/MeoAssistant/**'
- 'src/MeoAssistantBuilder/**'
- 'src/MeoAsstGui/**'
- '3rdparty/**'
- 'include/**'
- 'resource/**'
- MeoAssistantArknights.sln
workflow_dispatch:
inputs:
Reason:
description: "Reason"
required: true
jobs:
windows-latest:
name: windows-latest
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- name: Cache .nuke/temp, ~/.nuget/packages
uses: actions/cache@v2
with:
path: |
.nuke/temp
~/.nuget/packages
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj') }}
- name: Run './build.cmd DevBuild'
run: ./build.cmd DevBuild
env:
Reason: ${{ github.event.inputs.Reason }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/upload-artifact@v1
with:
name: artifacts
path: artifacts

View File

@@ -1,61 +0,0 @@
name: "Maa Dev Build"
on:
push:
branches:
- dev
paths:
- src/MeoAssistant/**
- src/MeoAssistantBuilder/**
- src/MeoAsstGui/**
- 3rdparty/**
- include/**
- MeoAssistantArknights.sln
pull_request:
paths:
- src/MeoAssistant/**
- src/MeoAssistantBuilder/**
- src/MeoAsstGui/**
- 3rdparty/**
- include/**
- MeoAssistantArknights.sln
workflow_dispatch:
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup .NET 6.0.x
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Run dev build task
run: pwsh .\build.ps1 DevBuild
- name: Read file name
id: read_path
run: pwsh .\read_path.ps1 ./artifacts MaaBundle*
- name: Upload Artifacts 0
uses: actions/upload-artifact@v3
with:
name: ${{ steps.read_path.outputs.name_0 }}
path: ${{ steps.read_path.outputs.path_0 }}
- name: Upload Artifacts 1
uses: actions/upload-artifact@v3
with:
name: ${{ steps.read_path.outputs.name_1 }}
path: ${{ steps.read_path.outputs.path_1 }}
- name: Upload Artifacts 2
uses: actions/upload-artifact@v3
with:
name: ${{ steps.read_path.outputs.name_2 }}
path: ${{ steps.read_path.outputs.path_2 }}

View File

@@ -1,45 +0,0 @@
name: Release MaaBundle
on:
release:
types:
- published
jobs:
release-bundle:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Get tag
id: tag
uses: dawidd6/action-get-tag@v1
- name: Setup .NET 6.0.x
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Run release core task
run: pwsh .\build.ps1 ReleaseBundle
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: MaaBundle-${{ steps.tag.outputs.tag }}
path: ./artifacts/MaaBundle*
- name: Upload release assets
uses: xresloader/upload-to-github-release@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
file: ./artifacts/MaaBundle*
tags: true
draft: false
verbose: true

View File

@@ -1,45 +0,0 @@
name: Release MaaCore
on:
release:
types:
- published
jobs:
release-core:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Get tag
id: tag
uses: dawidd6/action-get-tag@v1
- name: Setup .NET 6.0.x
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Run release core task
run: pwsh .\build.ps1 ReleaseCore
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: MaaCore-${{ steps.tag.outputs.tag }}
path: ./artifacts/MaaCore*
- name: Upload release assets
uses: xresloader/upload-to-github-release@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
file: ./artifacts/MaaCore*
tags: true
draft: false
verbose: true

View File

@@ -1,39 +0,0 @@
name: Release MaaResource
on:
push:
branches:
- master
paths:
- resource/**
- 3rdparty/resource/**
workflow_dispatch:
jobs:
release-resource:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup .NET 6.0.x
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Run release resource task
run: pwsh .\build.ps1 ReleaseResource
- name: Read file name
id: read_path
run: pwsh .\read_path.ps1 ./artifacts MaaResource*
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: ${{ steps.read_path.outputs.name_0 }}
path: ${{ steps.read_path.outputs.path_0 }}

View File

@@ -1,38 +0,0 @@
name: Release MaaWpf
on:
push:
branches:
- master
paths:
- src/MeoAsstGui/**
workflow_dispatch:
jobs:
release-wpf:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup .NET 6.0.x
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Run release wpf task
run: pwsh .\build.ps1 ReleaseWpf
- name: Read file name
id: read_path
run: pwsh .\read_path.ps1 ./artifacts MaaWpf*
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: ${{ steps.read_path.outputs.name_0 }}
path: ${{ steps.read_path.outputs.path_0 }}

View File

@@ -0,0 +1,48 @@
# ------------------------------------------------------------------------------
# <auto-generated>
#
# This code was generated.
#
# - To turn off auto-generation set:
#
# [GitHubActions (AutoGenerate = false)]
#
# - To trigger manual generation invoke:
#
# nuke --generate-configuration GitHubActions_release-maa-resource --host GitHubActions
#
# </auto-generated>
# ------------------------------------------------------------------------------
name: release-maa-resource
on:
push:
branches:
- master
paths:
- 'resource/**'
- '3rdparty/resource/**'
jobs:
windows-latest:
name: windows-latest
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- name: Cache .nuke/temp, ~/.nuget/packages
uses: actions/cache@v2
with:
path: |
.nuke/temp
~/.nuget/packages
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj') }}
- name: Run './build.cmd ReleaseMaaResource'
run: ./build.cmd ReleaseMaaResource
env:
PUBLISH_GH_PAT: ${{ secrets.PUBLISH_GH_PAT }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/upload-artifact@v1
with:
name: artifacts
path: artifacts

45
.github/workflows/release-maa.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
# ------------------------------------------------------------------------------
# <auto-generated>
#
# This code was generated.
#
# - To turn off auto-generation set:
#
# [GitHubActions (AutoGenerate = false)]
#
# - To trigger manual generation invoke:
#
# nuke --generate-configuration GitHubActions_release-maa --host GitHubActions
#
# </auto-generated>
# ------------------------------------------------------------------------------
name: release-maa
on:
push:
tags:
- 'v*'
jobs:
windows-latest:
name: windows-latest
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- name: Cache .nuke/temp, ~/.nuget/packages
uses: actions/cache@v2
with:
path: |
.nuke/temp
~/.nuget/packages
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj') }}
- name: Run './build.cmd ReleaseMaa'
run: ./build.cmd ReleaseMaa
env:
PUBLISH_GH_PAT: ${{ secrets.PUBLISH_GH_PAT }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/upload-artifact@v1
with:
name: artifacts
path: artifacts

3
.gitignore vendored
View File

@@ -437,3 +437,6 @@ tools/**/*.png
resource/infrast
.vscode
!3rdparty/tools/*
# Nuke
.nuke/temp/*

145
.nuke/build.schema.json Normal file
View File

@@ -0,0 +1,145 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Build Schema",
"$ref": "#/definitions/build",
"definitions": {
"build": {
"type": "object",
"properties": {
"Continue": {
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
},
"Help": {
"type": "boolean",
"description": "Shows the help text for this build assembly"
},
"Host": {
"type": "string",
"description": "Host for execution. Default is 'automatic'",
"enum": [
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitrise",
"GitHubActions",
"GitLab",
"Jenkins",
"Rider",
"SpaceAutomation",
"TeamCity",
"Terminal",
"TravisCI",
"VisualStudio",
"VSCode"
]
},
"NoLogo": {
"type": "boolean",
"description": "Disables displaying the NUKE logo"
},
"Partition": {
"type": "string",
"description": "Partition to use on CI"
},
"Plan": {
"type": "boolean",
"description": "Shows the execution plan (HTML)"
},
"Profile": {
"type": "array",
"description": "Defines the profiles to load",
"items": {
"type": "string"
}
},
"Root": {
"type": "string",
"description": "Root directory during build execution"
},
"Skip": {
"type": "array",
"description": "List of targets to be skipped. Empty list skips all dependencies",
"items": {
"type": "string",
"enum": [
"Clean",
"Default",
"DevBuild",
"ReleaseMaa",
"ReleaseMaaCore",
"ReleaseMaaResource",
"SetMaaChangeLog",
"SetPackageBundled",
"SetVersion",
"UseCommitVersion",
"UseMaaChangeLog",
"UseMaaCore",
"UseMaaDevBundle",
"UseMaaLegacyBundle",
"UseMaaResource",
"UseMaaResourceChangeLog",
"UsePublishArtifact",
"UsePublishRelease",
"UseTagVersion",
"WithCompileCoreCICD",
"WithCompileCoreDebug",
"WithCompileCoreRelease",
"WithCompileResourceRelease",
"WithCompileWpfDebug",
"WithCompileWpfRelease"
]
}
},
"Solution": {
"type": "string",
"description": "Path to a solution file that is automatically loaded"
},
"Target": {
"type": "array",
"description": "List of targets to be invoked. Default is '{default_target}'",
"items": {
"type": "string",
"enum": [
"Clean",
"Default",
"DevBuild",
"ReleaseMaa",
"ReleaseMaaCore",
"ReleaseMaaResource",
"SetMaaChangeLog",
"SetPackageBundled",
"SetVersion",
"UseCommitVersion",
"UseMaaChangeLog",
"UseMaaCore",
"UseMaaDevBundle",
"UseMaaLegacyBundle",
"UseMaaResource",
"UseMaaResourceChangeLog",
"UsePublishArtifact",
"UsePublishRelease",
"UseTagVersion",
"WithCompileCoreCICD",
"WithCompileCoreDebug",
"WithCompileCoreRelease",
"WithCompileResourceRelease",
"WithCompileWpfDebug",
"WithCompileWpfRelease"
]
}
},
"Verbosity": {
"type": "string",
"description": "Logging verbosity during build execution. Default is 'Normal'",
"enum": [
"Minimal",
"Normal",
"Quiet",
"Verbose"
]
}
}
}
}
}

4
.nuke/parameters.json Normal file
View File

@@ -0,0 +1,4 @@
{
"$schema": "./build.schema.json",
"Solution": "MeoAssistantArknights.sln"
}

2
CHANGELOG_MAA.md Normal file
View File

@@ -0,0 +1,2 @@
- 这是 MaaCore 的更新日志
- 这是用来测试的

2
CHANGELOG_RES.md Normal file
View File

@@ -0,0 +1,2 @@
- MaaResource 的更新日志
- 这是用来测试的

View File

@@ -29,49 +29,38 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
CICD|x64 = CICD|x64
Debug|x64 = Debug|x64
Release|x64 = Release|x64
RelWithDebInfo|x64 = RelWithDebInfo|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{63B4F1A2-291C-4C85-91E1-A1F6DAE30D64}.CICD|x64.ActiveCfg = RelWithDebInfo|x64
{63B4F1A2-291C-4C85-91E1-A1F6DAE30D64}.CICD|x64.Build.0 = RelWithDebInfo|x64
{63B4F1A2-291C-4C85-91E1-A1F6DAE30D64}.Debug|x64.ActiveCfg = Release|x64
{63B4F1A2-291C-4C85-91E1-A1F6DAE30D64}.Debug|x64.Build.0 = Release|x64
{63B4F1A2-291C-4C85-91E1-A1F6DAE30D64}.Release|x64.ActiveCfg = Release|x64
{63B4F1A2-291C-4C85-91E1-A1F6DAE30D64}.Release|x64.Build.0 = Release|x64
{63B4F1A2-291C-4C85-91E1-A1F6DAE30D64}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{63B4F1A2-291C-4C85-91E1-A1F6DAE30D64}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{FFDC8F49-8EAF-45BE-B0A8-7EF0DB9875A2}.CICD|x64.ActiveCfg = CICD|x64
{FFDC8F49-8EAF-45BE-B0A8-7EF0DB9875A2}.CICD|x64.Build.0 = CICD|x64
{FFDC8F49-8EAF-45BE-B0A8-7EF0DB9875A2}.Debug|x64.ActiveCfg = RelWithDebInfo|x64
{FFDC8F49-8EAF-45BE-B0A8-7EF0DB9875A2}.Debug|x64.Build.0 = RelWithDebInfo|x64
{FFDC8F49-8EAF-45BE-B0A8-7EF0DB9875A2}.Release|x64.ActiveCfg = Release|x64
{FFDC8F49-8EAF-45BE-B0A8-7EF0DB9875A2}.Release|x64.Build.0 = Release|x64
{FFDC8F49-8EAF-45BE-B0A8-7EF0DB9875A2}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{FFDC8F49-8EAF-45BE-B0A8-7EF0DB9875A2}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{093A3174-A27E-4D23-B64E-16F9448AD5AC}.CICD|x64.ActiveCfg = Release|x64
{093A3174-A27E-4D23-B64E-16F9448AD5AC}.CICD|x64.Build.0 = Release|x64
{093A3174-A27E-4D23-B64E-16F9448AD5AC}.Debug|x64.ActiveCfg = Release|x64
{093A3174-A27E-4D23-B64E-16F9448AD5AC}.Debug|x64.Build.0 = Release|x64
{093A3174-A27E-4D23-B64E-16F9448AD5AC}.Release|x64.ActiveCfg = Release|x64
{093A3174-A27E-4D23-B64E-16F9448AD5AC}.Release|x64.Build.0 = Release|x64
{093A3174-A27E-4D23-B64E-16F9448AD5AC}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{362D1E30-F5AE-4279-9985-65C27B3BA300}.CICD|x64.ActiveCfg = CICD|x64
{362D1E30-F5AE-4279-9985-65C27B3BA300}.CICD|x64.Build.0 = CICD|x64
{362D1E30-F5AE-4279-9985-65C27B3BA300}.Debug|x64.ActiveCfg = Release|x64
{362D1E30-F5AE-4279-9985-65C27B3BA300}.Debug|x64.Build.0 = Release|x64
{362D1E30-F5AE-4279-9985-65C27B3BA300}.Release|x64.ActiveCfg = Release|x64
{362D1E30-F5AE-4279-9985-65C27B3BA300}.Release|x64.Build.0 = Release|x64
{362D1E30-F5AE-4279-9985-65C27B3BA300}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{362D1E30-F5AE-4279-9985-65C27B3BA300}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{8D9036DD-75F1-4BD0-89E2-DFB37B925DA3}.CICD|x64.ActiveCfg = Release|x64
{8D9036DD-75F1-4BD0-89E2-DFB37B925DA3}.CICD|x64.Build.0 = Release|x64
{8D9036DD-75F1-4BD0-89E2-DFB37B925DA3}.Debug|x64.ActiveCfg = Debug|x64
{8D9036DD-75F1-4BD0-89E2-DFB37B925DA3}.Debug|x64.Build.0 = Debug|x64
{8D9036DD-75F1-4BD0-89E2-DFB37B925DA3}.Release|x64.ActiveCfg = Release|x64
{8D9036DD-75F1-4BD0-89E2-DFB37B925DA3}.Release|x64.Build.0 = Release|x64
{8D9036DD-75F1-4BD0-89E2-DFB37B925DA3}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{8D9036DD-75F1-4BD0-89E2-DFB37B925DA3}.CICD|x64.ActiveCfg = Release|Any CPU
{8D9036DD-75F1-4BD0-89E2-DFB37B925DA3}.CICD|x64.Build.0 = Release|Any CPU
{8D9036DD-75F1-4BD0-89E2-DFB37B925DA3}.Release|x64.ActiveCfg = Release|Any CPU
{8D9036DD-75F1-4BD0-89E2-DFB37B925DA3}.Release|x64.Build.0 = Release|Any CPU
{8D9036DD-75F1-4BD0-89E2-DFB37B925DA3}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

7
build.cmd Normal file
View File

@@ -0,0 +1,7 @@
:; set -eo pipefail
:; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
:; ${SCRIPT_DIR}/build.sh "$@"
:; exit $?
@ECHO OFF
powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*

View File

@@ -1,16 +1,69 @@
if ($args.Length -ne 1) {
throw "One argument required"
[CmdletBinding()]
Param(
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
[string[]]$BuildArguments
)
Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)"
Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 }
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
###########################################################################
# CONFIGURATION
###########################################################################
$BuildProjectFile = "$PSScriptRoot\src\MeoAssistantBuilder\MeoAssistantBuilder.csproj"
$TempDirectory = "$PSScriptRoot\\.nuke\temp"
$DotNetGlobalFile = "$PSScriptRoot\\global.json"
$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
$DotNetChannel = "Current"
$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
$env:DOTNET_MULTILEVEL_LOOKUP = 0
###########################################################################
# EXECUTION
###########################################################################
function ExecSafe([scriptblock] $cmd) {
& $cmd
if ($LASTEXITCODE) { exit $LASTEXITCODE }
}
$target = $args[0]
$valid_targets = @("ReleaseCore", "ReleaseWpf", "ReleaseResource", "ReleaseBundle", "DevBuild")
# If dotnet CLI is installed globally and it matches requested version, use for execution
if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
$(dotnet --version) -and $LASTEXITCODE -eq 0) {
$env:DOTNET_EXE = (Get-Command "dotnet").Path
}
else {
# Download install script
$DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
(New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
if (!$valid_targets.Contains($target)) {
throw "$target is not a valid target"
# If global.json exists, load expected version
if (Test-Path $DotNetGlobalFile) {
$DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
$DotNetVersion = $DotNetGlobal.sdk.version
}
}
# Install by channel or version
$DotNetDirectory = "$TempDirectory\dotnet-win"
if (!(Test-Path variable:DotNetVersion)) {
ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
} else {
ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
}
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
}
Set-Location -Path .\src\MeoAssistantBuilder
Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)"
dotnet run -- --target $target
Set-Location -Path ..\..\
ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }

62
build.sh Normal file
View File

@@ -0,0 +1,62 @@
#!/usr/bin/env bash
bash --version 2>&1 | head -n 1
set -eo pipefail
SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
###########################################################################
# CONFIGURATION
###########################################################################
BUILD_PROJECT_FILE="$SCRIPT_DIR/src/MeoAssistantBuilder/MeoAssistantBuilder.csproj"
TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
DOTNET_CHANNEL="Current"
export DOTNET_CLI_TELEMETRY_OPTOUT=1
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export DOTNET_MULTILEVEL_LOOKUP=0
###########################################################################
# EXECUTION
###########################################################################
function FirstJsonValue {
perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}"
}
# If dotnet CLI is installed globally and it matches requested version, use for execution
if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then
export DOTNET_EXE="$(command -v dotnet)"
else
# Download install script
DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
mkdir -p "$TEMP_DIRECTORY"
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
chmod +x "$DOTNET_INSTALL_FILE"
# If global.json exists, load expected version
if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then
DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")")
if [[ "$DOTNET_VERSION" == "" ]]; then
unset DOTNET_VERSION
fi
fi
# Install by channel or version
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
if [[ -z ${DOTNET_VERSION+x} ]]; then
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
else
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
fi
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
fi
echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)"
"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"

View File

@@ -1,212 +1,212 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "maa task config",
"patternProperties": {
"^(?!\\$)": {
"type": "object",
"oneOf": [
{
"$ref": "#/definitions/JustReturnTask"
},
{
"$ref": "#/definitions/MatchTemplateTask"
},
{
"$ref": "#/definitions/OcrDetectTask"
},
{
"$ref": "#/definitions/HashTask"
}
]
}
},
"definitions": {
"Rectangle": {
"type": "array",
"maxItems": 4,
"minItems": 4,
"items": {
"type": "number"
}
},
"TaskNameList": {
"type": "array",
"items": {
"type": "string",
"description": "要执行的任务"
}
},
"BaseTask": {
"properties": {
"algorithm": {
"type": "string",
"description": "算法类型"
},
"action": {
"type": "string",
"pattern": "ClickSelf|ClickRand|DoNothing|Stop|ClickRect|SwipeToTheLeft|SwipeToTheRight",
"default": "DoNothing",
"description": "可选项,表示识别到后的动作\n不填写时默认为 DoNothing"
},
"next": {
"$ref": "#/definitions/TaskNameList",
"description": "可选项,表示执行完当前任务后,下一个要执行的任务\n会从前往后依次去识别去执行第一个匹配上的\n不填写默认执行完当前任务直接停止"
},
"maxTimes": {
"type": "number",
"description": "可选项,表示该任务最大执行次数\n不填写时默认无穷大\n达到最大次数后若存在 exceededNext 字段,则执行 exceededNext否则直接任务停止"
},
"exceededNext": {
"$ref": "#/definitions/TaskNameList",
"description": "可选项,表示达到了最大执行次数后要执行的任务\n不填写时达到了最大执行次数则停止填写后就执行这里的而不是 next 里的"
},
"preDelay": {
"type": "number",
"description": "可选项,表示识别到后延迟多久才执行 action单位毫秒不填写时默认 0"
},
"rearDelay": {
"type": "number",
"description": "可选项,表示 action 执行完后延迟多久才去识别 next, 单位毫秒;不填写时默认 0"
},
"roi": {
"$ref": "#/definitions/Rectangle",
"description": "可选项,表示识别的范围,格式为 [ x, y, width, height ]\n以 1280 * 720 为基准自动缩放;不填写时默认 [ 0, 0, 1280, 720 ]\n尽量填写减小识别范围可以减少性能消耗加快识别速度"
},
"cache": {
"type": "boolean",
"default": true,
"description": "可选项,表示该任务是否使用缓存,默认为 true;\n第一次识别到后以后永远只在第一次识别到的位置进行识别开启可大幅节省性能\n但仅适用于待识别目标位置完全不会变的任务若待识别目标位置会变请设为 false"
},
"rectMove": {
"$ref": "#/definitions/Rectangle",
"description": "可选项,识别后的目标移动,不建议使用该选项。以 1280 * 720 为基准自动缩放\n例如识别到了 A 图标,但实际要点击的是 A 图标旁边的某个位置,则进行移动后再点击。可以的话尽量直接识别要点击的位置,不建议使用该选项"
},
"reduceOtherTimes": {
"type": "array",
"items": {
"type": "string",
"description": "要执行的任务"
},
"description": "可选项,执行后减少其他任务的执行计数。\n可选项执行后减少其他任务的执行计数。"
},
"specificRect": {
"$ref": "#/definitions/Rectangle",
"description": "仅当 action 为 ClickRect 时有效且必选,表示指定的点击位置(范围内随机一点)。以 1280 * 720 为基准自动缩放"
}
}
},
"JustReturnTask": {
"allOf": [
{
"$ref": "#/definitions/BaseTask"
}
],
"properties": {
"algorithm": {
"type": "string",
"pattern": "JustReturn"
}
},
"description": "不进行识别,直接执行 action",
"required": [
"algorithm"
]
},
"MatchTemplateTask": {
"allOf": [
{
"$ref": "#/definitions/BaseTask"
}
],
"properties": {
"algorithm": {
"type": "string",
"pattern": "MatchTemplate"
},
"template": {
"type": "string",
"default": "",
"description": "可选项,要匹配的图片文件名\n不填时写默认 \"任务名.png\""
},
"templThreshold": {
"type": "number",
"default": 0.8,
"description": "可选项,图片模板匹配得分的阈值,超过阈值才认为识别到了。\n默认 0.8, 可根据日志查看实际得分是多少"
},
"maskRange": {
"type": "array",
"maxItems": 2,
"minimum": 2,
"items": {
"type": "number"
},
"default": [
1,
255
],
"description": "可选项,灰度掩码范围。例如将图片不需要识别的部分涂成黑色(灰度值为 0然后设置 [ 1, 255 ], 匹配的时候即刻忽略涂黑的部分"
}
},
"description": "匹配图片"
},
"OcrDetectTask": {
"allOf": [
{
"$ref": "#/definitions/BaseTask"
}
],
"properties": {
"algorithm": {
"type": "string",
"pattern": "OcrDetect"
},
"text": {
"type": "array",
"items": {
"type": "string"
},
"description": "必选项,要识别的文字内容,只要任一匹配上了即认为识别到了"
},
"fullMatch": {
"type": "boolean",
"default": false,
"description": "可选项,文字识别是否需要全字匹配(不能多字),默认为 false\nfalse 时只要是子串即可:例如 text: [ \"开始\" ],实际识别到了 \"开始行动\",也算识别成功;\ntrue 时则必须识别到了 \"开始\",多一个字都不行"
},
"ocrReplace": {
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"type": "string"
}
},
"description": "可选项,针对常见识别错的文字进行替换(支持正则)"
}
},
"description": "文字识别",
"required": [
"algorithm",
"text"
]
},
"HashTask": {
"allOf": [
{
"$ref": "#/definitions/BaseTask"
}
],
"properties": {
"algorithm": {
"type": "string",
"pattern": "Hash"
}
},
"description": "哈希计算",
"required": [
"algorithm"
]
}
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "maa task config",
"patternProperties": {
"^(?!\\$)": {
"type": "object",
"oneOf": [
{
"$ref": "#/definitions/JustReturnTask"
},
{
"$ref": "#/definitions/MatchTemplateTask"
},
{
"$ref": "#/definitions/OcrDetectTask"
},
{
"$ref": "#/definitions/HashTask"
}
]
}
},
"definitions": {
"Rectangle": {
"type": "array",
"maxItems": 4,
"minItems": 4,
"items": {
"type": "number"
}
},
"TaskNameList": {
"type": "array",
"items": {
"type": "string",
"description": "要执行的任务"
}
},
"BaseTask": {
"properties": {
"algorithm": {
"type": "string",
"description": "算法类型"
},
"action": {
"type": "string",
"pattern": "ClickSelf|ClickRand|DoNothing|Stop|ClickRect|SwipeToTheLeft|SwipeToTheRight",
"default": "DoNothing",
"description": "可选项,表示识别到后的动作\n不填写时默认为 DoNothing"
},
"next": {
"$ref": "#/definitions/TaskNameList",
"description": "可选项,表示执行完当前任务后,下一个要执行的任务\n会从前往后依次去识别去执行第一个匹配上的\n不填写默认执行完当前任务直接停止"
},
"maxTimes": {
"type": "number",
"description": "可选项,表示该任务最大执行次数\n不填写时默认无穷大\n达到最大次数后若存在 exceededNext 字段,则执行 exceededNext否则直接任务停止"
},
"exceededNext": {
"$ref": "#/definitions/TaskNameList",
"description": "可选项,表示达到了最大执行次数后要执行的任务\n不填写时达到了最大执行次数则停止填写后就执行这里的而不是 next 里的"
},
"preDelay": {
"type": "number",
"description": "可选项,表示识别到后延迟多久才执行 action单位毫秒不填写时默认 0"
},
"rearDelay": {
"type": "number",
"description": "可选项,表示 action 执行完后延迟多久才去识别 next, 单位毫秒;不填写时默认 0"
},
"roi": {
"$ref": "#/definitions/Rectangle",
"description": "可选项,表示识别的范围,格式为 [ x, y, width, height ]\n以 1280 * 720 为基准自动缩放;不填写时默认 [ 0, 0, 1280, 720 ]\n尽量填写减小识别范围可以减少性能消耗加快识别速度"
},
"cache": {
"type": "boolean",
"default": true,
"description": "可选项,表示该任务是否使用缓存,默认为 true;\n第一次识别到后以后永远只在第一次识别到的位置进行识别开启可大幅节省性能\n但仅适用于待识别目标位置完全不会变的任务若待识别目标位置会变请设为 false"
},
"rectMove": {
"$ref": "#/definitions/Rectangle",
"description": "可选项,识别后的目标移动,不建议使用该选项。以 1280 * 720 为基准自动缩放\n例如识别到了 A 图标,但实际要点击的是 A 图标旁边的某个位置,则进行移动后再点击。可以的话尽量直接识别要点击的位置,不建议使用该选项"
},
"reduceOtherTimes": {
"type": "array",
"items": {
"type": "string",
"description": "要执行的任务"
},
"description": "可选项,执行后减少其他任务的执行计数。\n可选项执行后减少其他任务的执行计数。"
},
"specificRect": {
"$ref": "#/definitions/Rectangle",
"description": "仅当 action 为 ClickRect 时有效且必选,表示指定的点击位置(范围内随机一点)。以 1280 * 720 为基准自动缩放"
}
}
},
"JustReturnTask": {
"allOf": [
{
"$ref": "#/definitions/BaseTask"
}
],
"properties": {
"algorithm": {
"type": "string",
"pattern": "JustReturn"
}
},
"description": "不进行识别,直接执行 action",
"required": [
"algorithm"
]
},
"MatchTemplateTask": {
"allOf": [
{
"$ref": "#/definitions/BaseTask"
}
],
"properties": {
"algorithm": {
"type": "string",
"pattern": "MatchTemplate"
},
"template": {
"type": "string",
"default": "",
"description": "可选项,要匹配的图片文件名\n不填时写默认 \"任务名.png\""
},
"templThreshold": {
"type": "number",
"default": 0.8,
"description": "可选项,图片模板匹配得分的阈值,超过阈值才认为识别到了。\n默认 0.8, 可根据日志查看实际得分是多少"
},
"maskRange": {
"type": "array",
"maxItems": 2,
"minimum": 2,
"items": {
"type": "number"
},
"default": [
1,
255
],
"description": "可选项,灰度掩码范围。例如将图片不需要识别的部分涂成黑色(灰度值为 0然后设置 [ 1, 255 ], 匹配的时候即刻忽略涂黑的部分"
}
},
"description": "匹配图片"
},
"OcrDetectTask": {
"allOf": [
{
"$ref": "#/definitions/BaseTask"
}
],
"properties": {
"algorithm": {
"type": "string",
"pattern": "OcrDetect"
},
"text": {
"type": "array",
"items": {
"type": "string"
},
"description": "必选项,要识别的文字内容,只要任一匹配上了即认为识别到了"
},
"fullMatch": {
"type": "boolean",
"default": false,
"description": "可选项,文字识别是否需要全字匹配(不能多字),默认为 false\nfalse 时只要是子串即可:例如 text: [ \"开始\" ],实际识别到了 \"开始行动\",也算识别成功;\ntrue 时则必须识别到了 \"开始\",多一个字都不行"
},
"ocrReplace": {
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"type": "string"
}
},
"description": "可选项,针对常见识别错的文字进行替换(支持正则)"
}
},
"description": "文字识别",
"required": [
"algorithm",
"text"
]
},
"HashTask": {
"allOf": [
{
"$ref": "#/definitions/BaseTask"
}
],
"properties": {
"algorithm": {
"type": "string",
"pattern": "Hash"
}
},
"description": "哈希计算",
"required": [
"algorithm"
]
}
}
}

View File

@@ -1,16 +0,0 @@
if ($args.Length -ne 2) {
throw "Need two arguments"
}
$fs = Get-ChildItem -Path $args[0] -Filter $args[1]
$index = 0
foreach($f in $fs) {
$name = $f.Name
$path = $f.FullName
Write-Output "::set-output name=name_$index::$name"
Write-Output "::set-output name=path_$index::$path"
$index = $index + 1
}

View File

@@ -297,7 +297,7 @@
<LanguageStandard_C>stdc11</LanguageStandard_C>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<Optimization>MaxSpeed</Optimization>
<AdditionalOptions>/utf-8 /MP %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions>/utf-8 /MP $(ExternalCompilerOptions) %(AdditionalOptions)</AdditionalOptions>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
@@ -328,7 +328,7 @@
<Optimization>Disabled</Optimization>
<WholeProgramOptimization>true</WholeProgramOptimization>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<AdditionalOptions>/utf-8 /MP %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions>/utf-8 /MP $(ExternalCompilerOptions) %(AdditionalOptions)</AdditionalOptions>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
@@ -373,4 +373,4 @@
<UserProperties _1_1_4_1_1_4resource_4config_1json__JsonSchema="" _1_1_4_1_1_4resource_4tasks_1json__JsonSchema="../docs/maa_tasks_schema.json" />
</VisualStudio>
</ProjectExtensions>
</Project>
</Project>

View File

@@ -0,0 +1,11 @@
[*.cs]
dotnet_style_qualification_for_field = false:warning
dotnet_style_qualification_for_property = false:warning
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_event = false:warning
dotnet_style_require_accessibility_modifiers = never:warning
csharp_style_expression_bodied_methods = true:silent
csharp_style_expression_bodied_properties = true:warning
csharp_style_expression_bodied_indexers = true:warning
csharp_style_expression_bodied_accessors = true:warning

View File

@@ -0,0 +1,117 @@
using Nuke.Common.Tooling;
using System.ComponentModel;
namespace MeoAssistantBuilder;
[TypeConverter(typeof(TypeConverter<ActionConfiguration>))]
public class ActionConfiguration : Enumeration
{
/// <summary>
/// 开发构建测试
/// <para>触发条件</para>
/// <list type="bullet">
/// <item>Push 或 PR 至 `dev` 分支</item>
/// <item>Maa 相关代码有更改</item>
/// <item>可手动触发</item>
/// </list>
/// <para>运行内容 (在 MaaElectronUI 发布后将会进行修改)</para>
/// <list type="bullet">
/// <item>将会发布 Artifact</item>
/// <item>将会打包 MaaBundle</item>
/// <item>生成的包文件名为 `MaaBundle-Dev-{VERSION}.zip`</item>
/// <item>版本号为 `{yyyy-MM-dd-HH-mm-ss}-{commitHash[..7]}`</item>
/// <item>MaaCore 使用 `RelWithDebInfo` 配置</item>
/// <item>MaaWpf 使用 `RelWithDebInfo` 配置</item>
/// </list>
/// </summary>
public static readonly ActionConfiguration DevBuild = new() { Value = "dev-build" };
/// <summary>
/// 发布 MeoAssistantArknights (在 MaaElectronUI 发布后,此 Action 将被移除,由 <see cref="ReleaseMaaCore"/> 代替)
/// <para>触发条件</para>
/// <list type="bullet">
/// <item>创建了 Tag</item>
/// <item>Tag 匹配 `v*` 规则</item>
/// </list>
/// <para>运行内容</para>
/// <list type="bullet">
/// <item>将会发布 Artifact</item>
/// <item>将会打包 MeoAssistantArknights 和 MaaCore</item>
/// <item>生成的包文件名为 `MeoAssistantArknights_{VERSION}.zip` 和 `MaaCore-{VERSION}.zip`</item>
/// <item>GitHub Actions 中运行时,将会发布 Release 至 `MaaAssistantArknights/MaaAssistantArknights`</item>
/// <item>GitHub Actions 中运行时,`ref` 不匹配 `ref/tags/v*` 时将会抛错</item>
/// <item>GitHub Actions 中运行时,版本号为 `{Tag}`</item>
/// <item>
/// 其他环境中运行时,版本号为
/// <list type="number">
/// <item>当前 HEAD 存在一个 Tag则为 `{Tag}-Local`</item>
/// <item>当前 HEAD 不存在 Tag则为 `v.{CommitHash[..7]}-Local`</item>
/// </list>
/// </item>
/// <item>MaaCore 使用 `Release` 配置,单独的包中使用 `CICD` 配置</item>
/// <item>MaaWpf 使用 `Release` 配置</item>
/// <item>将会移除 Debug 用文件</item>
/// </list>
/// </summary>
public static readonly ActionConfiguration ReleaseMaa = new() { Value = "release-maa" };
/// <summary>
/// 发布 MaaCore (在 MaaElectronUI 发布前,此 Action 不执行)
/// <para>触发条件</para>
/// <list type="bullet">
/// <item>创建了 Tag</item>
/// <item>Tag 匹配 `v*` 规则</item>
/// </list>
/// <para>运行内容</para>
/// <list type="bullet">
/// <item>将会发布 Artifact</item>
/// <item>将会打包 MaaCore</item>
/// <item>生成的包文件名为 `MaaCore-{VERSION}.zip`</item>
/// <item>GitHub Actions 中运行时,将会发布 Release 至 `MaaAssistantArknights/MaaAssistantArknights`</item>
/// <item>GitHub Actions 中运行时,`ref` 不匹配 `ref/tags/v*` 时将会抛错</item>
/// <item>GitHub Actions 中运行时,版本号为 `{Tag}`</item>
/// <item>
/// 其他环境中运行时,版本号为
/// <list type="number">
/// <item>当前 HEAD 存在一个 Tag则为 `{Tag}-Local`</item>
/// <item>当前 HEAD 不存在 Tag则为 `v.{CommitHash[..7]}-Local`</item>
/// </list>
/// </item>
/// <item>MaaCore 使用 `CICD` 配置</item>
/// <item>将会移除 Debug 用文件</item>
/// </list>
/// </summary>
public static readonly ActionConfiguration ReleaseMaaCore = new() { Value = "release-maa-core" };
/// <summary>
/// 发布 MaaResource
/// <para>触发条件</para>
/// <list type="bullet">
/// <item>资源文件夹有更改</item>
/// <item>`master` 分支</item>
/// </list>
/// <para>运行内容</para>
/// <list type="bullet">
/// <item>将会发布 Artifact</item>
/// <item>将会打包 MaaResource</item>
/// <item>生成的包文件名为 `MaaResource-{VERSION}.zip`</item>
/// <item>GitHub Actions 中运行时,将会发布 Release 至 `MaaAssistantArknights/MaaResourceRelease`</item>
/// <item>版本号为 `{yyyy-MM-dd-HH-mm-ss}-{commitHash[..7]}`</item>
/// </list>
/// </summary>
public static readonly ActionConfiguration ReleaseMaaResource = new() { Value = "release-maa-resource" };
public static implicit operator string(ActionConfiguration configuration)
{
return configuration.Value;
}
public static explicit operator ActionConfiguration(string value) => value switch
{
"dev-build" => DevBuild,
"release-maa" => ReleaseMaa,
"release-maa-core" => ReleaseMaaCore,
"release-maa-resource" => ReleaseMaaResource,
_ => null
};
}

View File

@@ -0,0 +1,35 @@
using Nuke.Common.CI.GitHubActions;
namespace MeoAssistantBuilder;
[GitHubActions(
name: "dev-build",
image: GitHubActionsImage.WindowsLatest,
OnPushBranchesIgnore = new[] { MasterBranch },
OnPullRequestBranches = new[] { DevBranch },
OnPushIncludePaths = new[]
{
"src/MeoAssistant/**",
"src/MeoAssistantBuilder/**",
"src/MeoAsstGui/**", // 新 UI 发布后,移除 MeoAsstGui
"3rdparty/**", // 新 UI 发布后,移除 3rdparty/resource/**
"include/**",
"resource/**", // 新 UI 发布后DevBuild 不再包含 WPF Gui 内容,不再打包 MaaBundle因此移除资源文件夹的监控
"MeoAssistantArknights.sln"
},
OnPullRequestIncludePaths = new[]
{
"src/MeoAssistant/**",
"src/MeoAssistantBuilder/**",
"src/MeoAsstGui/**", // 新 UI 发布后,移除 MeoAsstGui
"3rdparty/**", // 新 UI 发布后,移除 3rdparty/resource/**
"include/**",
"resource/**", // 新 UI 发布后DevBuild 不再包含 WPF Gui 内容,不再打包 MaaBundle因此移除资源文件夹的监控
"MeoAssistantArknights.sln"
},
OnWorkflowDispatchRequiredInputs = new string[] { "Reason" },
InvokedTargets = new[] { nameof(DevBuild) },
EnableGitHubToken = true,
PublishArtifacts = true
)]
public partial class Build { }

View File

@@ -0,0 +1,14 @@
using Nuke.Common.CI.GitHubActions;
namespace MeoAssistantBuilder;
[GitHubActions(
name: "release-maa",
image: GitHubActionsImage.WindowsLatest,
OnPushTags = new[] { "v*" },
InvokedTargets = new[] { nameof(ReleaseMaa) },
ImportSecrets = new[] { "PUBLISH_GH_PAT" },
EnableGitHubToken = true,
PublishArtifacts = true
)]
public partial class Build { }

View File

@@ -0,0 +1,17 @@
/*using Nuke.Common.CI.GitHubActions;
namespace MeoAssistantBuilder;
[GitHubActions(
name: "release-maa-core",
image: GitHubActionsImage.WindowsLatest,
OnPushTags = new[] { "v*" },
InvokedTargets = new[] { nameof(ReleaseMaaCore) },
ImportSecrets = new[] { "PUBLISH_GH_PAT" },
EnableGitHubToken = true,
PublishArtifacts = true
)]
public partial class Build { }
*/
// 在 MaaElectronUI 发布前不启用

View File

@@ -0,0 +1,19 @@
using Nuke.Common.CI.GitHubActions;
namespace MeoAssistantBuilder;
[GitHubActions(
name: "release-maa-resource",
image: GitHubActionsImage.WindowsLatest,
OnPushBranches = new[] { MasterBranch },
OnPushIncludePaths = new[]
{
"resource/**",
"3rdparty/resource/**"
},
InvokedTargets = new[] { nameof(ReleaseMaaResource) },
ImportSecrets = new[] { "PUBLISH_GH_PAT" },
EnableGitHubToken = true,
PublishArtifacts = true
)]
public partial class Build { }

View File

@@ -0,0 +1,588 @@
using Microsoft.AspNetCore.StaticFiles;
using Nuke.Common;
using Nuke.Common.Execution;
using Nuke.Common.Git;
using Nuke.Common.IO;
using Nuke.Common.Tooling;
using Nuke.Common.Tools.GitHub;
using Nuke.Common.Tools.MSBuild;
using Octokit;
using Serilog;
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.InteropServices;
using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
namespace MeoAssistantBuilder;
[CheckBuildProjectConfigurations]
public partial class Build : NukeBuild
{
public static int Main()
{
var osValidation =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
RuntimeInformation.OSArchitecture == Architecture.X64;
if (osValidation is false)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Error.WriteLine("仅在 Windows x64 平台可用");
Console.Error.WriteLine($"当前系统:{RuntimeInformation.OSDescription}");
Console.Error.WriteLine($"当前系统架构:{RuntimeInformation.OSArchitecture}");
return 1;
}
return Execute<Build>(_ => _.Default);
}
BuildParameters Parameters;
const string MasterBranch = "master";
const string DevBranch = "dev";
const string MaaDevBundlePackageNameTemplate = "MaaBundle-Dev-{VERSION}";
const string MaaLegacyBundlePackageNameTemplate = "MeoAssistantArknights_{VERSION}";
const string MaaCorePackageNameTemplate = "MaaCore-{VERSION}";
const string MaaResourcePackageNameTemplate = "MaaResource-{VERSION}";
private string _version = "";
private string _changeLog = "";
private string MaaDevBundlePackageName => MaaDevBundlePackageNameTemplate.Replace("{VERSION}", _version);
private string MaaLegacyBundlePackageName => MaaLegacyBundlePackageNameTemplate.Replace("{VERSION}", _version);
private string MaaCorePackageName => MaaCorePackageNameTemplate.Replace("{VERSION}", _version);
private string MaaResourcePackageName => MaaResourcePackageNameTemplate.Replace("{VERSION}", _version);
protected override void OnBuildInitialized()
{
Parameters = new BuildParameters(this);
Information("==========================");
Information("1. 工具链");
Information($"Visual Studio 路径:{Parameters.VisualStudioPath ?? "Null"}");
Information($"MSBuild 路径:{Parameters.MsBuildPath ?? "Null"}");
Information("2. 仓库");
Information($"Fork{Parameters.IsFork}");
Information($"主仓库:{Parameters.MainRepo ?? "Null"}");
Information($"MaaResource 发布仓库:{Parameters.MaaResourceReleaseRepo ?? "Null"}");
Information($"主分支:{Parameters.MasterBranchRef ?? "Null"}");
Information($"开发分支:{Parameters.DevBranchRef ?? "Null"}");
Information($"发布 Tag 前缀:{Parameters.ReleaseTagRefPrefix ?? "Null"}");
Information("3. 路径");
Information($"编译输出路径:{Parameters.BuildOutput ?? "Null"}");
Information($"Artifact 输出路径:{Parameters.ArtifactOutput ?? "Null"}");
Information("4. 项目");
Information($"MaaCore 项目:{Parameters.MaaCoreProject ?? "Null"}");
Information($"MaaWpf 项目:{Parameters.MaaWpfProject ?? "Null"}");
Information("5. 配置");
Information($"构建时间:{Parameters.BuildTime ?? "Null"}");
Information($"Commit Hash{Parameters.CommitHash ?? "Null"}");
Information($"Commit Hash Full{Parameters.CommitHashFull ?? "Null"}");
Information("6. CI");
Information($"在 GitHub Actions 中运行:{Parameters.IsGitHubActions}");
Information($"是 Pull Request{Parameters.IsPullRequest}");
Information($"是 Workflow Dispatch 触发:{Parameters.IsWorkflowDispatch}");
Information($"Actions 名称:{Parameters.GhActionName ?? "Null"}");
Information($"Actions 分支:{Parameters.GhBranch ?? "Null"}");
Information($"Actions PR{Parameters.GhPullRequestId ?? "Null"}");
Information($"Actions Tag{Parameters.GhTag ?? "Null"}");
Information("==========================");
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>")]
Target Default => _ => _
.Executes(() =>
{
Assert.Fail("请指定一个 Target");
});
#region
Target Clean => _ => _
.Executes(() =>
{
FileSystemTasks.EnsureCleanDirectory(Parameters.ArtifactOutput);
FileSystemTasks.EnsureCleanDirectory(Parameters.BuildOutput / BuildConfiguration.Release);
FileSystemTasks.EnsureCleanDirectory(Parameters.BuildOutput / BuildConfiguration.RelWithDebInfo);
FileSystemTasks.EnsureCleanDirectory(Parameters.BuildOutput / BuildConfiguration.CICD);
});
#endregion
#region
Target UseCommitVersion => _ => _
.DependsOn(Clean)
.Triggers(SetVersion)
.Executes(() =>
{
_version = $"{Parameters.BuildTime}-{Parameters.CommitHash}";
});
Target UseTagVersion => _ => _
.DependsOn(Clean)
.Triggers(SetVersion)
.Executes(() =>
{
if (Parameters.IsGitHubActions is false)
{
Information("未在 GitHub Actions 中运行");
var repo = GitRepository.FromLocalDirectory(RootDirectory);
Assert.True(repo is not null, "不在 Git Repo 中");
var tag = repo.Tags.FirstOrDefault();
if (tag is not null)
{
_version = $"{tag}-Local";
}
else
{
_version = $"v.{Parameters.CommitHash}-Local";
}
}
else
{
Information("在 GitHub Actions 中运行");
Assert.True(Parameters.GhTag is not null, "在 GitHub Actions 中运行,但是不存在 Tag");
_version = Parameters.GhTag;
}
});
Target SetVersion => _ => _
.Executes(() =>
{
Information($"当前版本号设置为:{_version}");
});
#endregion
#region
Target WithCompileCoreRelease => _ => _
.After(SetVersion)
.Executes(() =>
{
var versionEnv = $"/DMAA_VERSION=\\\"{_version}\\\"";
Information($"MaaCore 编译环境变量ExternalCompilerOptions = {versionEnv}");
MSBuild(c => c
.SetProcessToolPath(Parameters.MsBuildPath)
.SetProjectFile(Parameters.MaaCoreProject)
.SetTargets("ReBuild")
.SetConfiguration(BuildConfiguration.Release)
.SetTargetPlatform(MSBuildTargetPlatform.x64)
.SetProcessEnvironmentVariable("ExternalCompilerOptions", versionEnv)
);
});
Target WithCompileCoreDebug => _ => _
.After(SetVersion)
.Executes(() =>
{
var versionEnv = $"/DMAA_VERSION=\\\"{_version}\\\"";
Information($"MaaCore 编译环境变量ExternalCompilerOptions = {versionEnv}");
MSBuild(c => c
.SetProcessToolPath(Parameters.MsBuildPath)
.SetProjectFile(Parameters.MaaCoreProject)
.SetTargets("ReBuild")
.SetConfiguration(BuildConfiguration.RelWithDebInfo)
.SetTargetPlatform(MSBuildTargetPlatform.x64)
.SetProcessEnvironmentVariable("ExternalCompilerOptions", versionEnv)
);
});
Target WithCompileCoreCICD => _ => _
.After(SetVersion)
.Executes(() =>
{
var versionEnv = $"/DMAA_VERSION=\\\"{_version}\\\"";
Information($"MaaCore 编译环境变量ExternalCompilerOptions = {versionEnv}");
MSBuild(c => c
.SetProcessToolPath(Parameters.MsBuildPath)
.SetProjectFile(Parameters.MaaCoreProject)
.SetTargets("ReBuild")
.SetConfiguration(BuildConfiguration.CICD)
.SetTargetPlatform(MSBuildTargetPlatform.x64)
.SetProcessEnvironmentVariable("ExternalCompilerOptions", versionEnv)
);
var output = Parameters.BuildOutput / BuildConfiguration.CICD;
var dlls = RootDirectory / "3rdparty" / "bin";
Information($"复制目录:{dlls} -> {output}");
FileSystemTasks.CopyDirectoryRecursively(dlls, output,
DirectoryExistsPolicy.Merge, FileExistsPolicy.OverwriteIfNewer);
});
// TODO 在 MaaElectronUI 发布后移除
Target WithCompileWpfRelease => _ => _
.After(SetVersion)
.Executes(() =>
{
MSBuild(c => c
.SetProcessToolPath(Parameters.MsBuildPath)
.SetProjectFile(Parameters.MaaWpfProject)
.SetTargets("ReBuild")
.SetConfiguration(BuildConfiguration.Release)
.SetTargetPlatform(MSBuildTargetPlatform.x64)
.EnableRestore()
);
});
// TODO 在 MaaElectronUI 发布后移除
Target WithCompileWpfDebug => _ => _
.After(SetVersion)
.Executes(() =>
{
MSBuild(c => c
.SetProcessToolPath(Parameters.MsBuildPath)
.SetProjectFile(Parameters.MaaWpfProject)
.SetTargets("ReBuild")
.SetConfiguration(BuildConfiguration.RelWithDebInfo)
.SetTargetPlatform(MSBuildTargetPlatform.x64)
.EnableRestore()
);
});
Target WithCompileResourceRelease => _ => _
.After(SetVersion)
.Executes(() =>
{
var output = Parameters.BuildOutput / BuildConfiguration.Release;
var resourceFile = RootDirectory / "resource";
var resourceFileThirdParty = RootDirectory / "3rdparty" / "resource";
Information($"复制目录:{resourceFile} -> {output}");
FileSystemTasks.CopyDirectoryRecursively(resourceFile, output,
DirectoryExistsPolicy.Merge, FileExistsPolicy.OverwriteIfNewer);
Information($"复制目录:{resourceFileThirdParty} -> {output}");
FileSystemTasks.CopyDirectoryRecursively(resourceFileThirdParty, output,
DirectoryExistsPolicy.Merge, FileExistsPolicy.OverwriteIfNewer);
});
#endregion
#region
Target UseMaaDevBundle => _ => _
.DependsOn(WithCompileCoreDebug, WithCompileWpfDebug)
.Triggers(SetPackageBundled)
.Executes(() =>
{
var buildOutput = Parameters.BuildOutput / BuildConfiguration.RelWithDebInfo;
BundlePackage(buildOutput, MaaDevBundlePackageName);
});
Target UseMaaLegacyBundle => _ => _
.DependsOn(WithCompileCoreCICD, WithCompileCoreRelease, WithCompileWpfRelease)
.Triggers(SetPackageBundled)
.Executes(() =>
{
var releaseBuildOutput = Parameters.BuildOutput / BuildConfiguration.Release;
RemoveDebugSymbols(releaseBuildOutput);
BundlePackage(releaseBuildOutput, MaaLegacyBundlePackageName);
var cicdBuildOutput = Parameters.BuildOutput / BuildConfiguration.CICD;
RemoveDebugSymbols(cicdBuildOutput);
BundlePackage(cicdBuildOutput, MaaCorePackageName);
});
Target UseMaaCore => _ => _
.DependsOn(WithCompileCoreCICD)
.Triggers(SetPackageBundled)
.Executes(() =>
{
var buildOutput = Parameters.BuildOutput / BuildConfiguration.CICD;
RemoveDebugSymbols(buildOutput);
BundlePackage(buildOutput, MaaCorePackageName);
});
Target UseMaaResource => _ => _
.DependsOn(WithCompileResourceRelease)
.Triggers(SetPackageBundled)
.Executes(() =>
{
var buildOutput = Parameters.BuildOutput / BuildConfiguration.Release;
RemoveDebugSymbols(buildOutput);
BundlePackage(buildOutput, MaaResourcePackageName);
});
Target SetPackageBundled => _ => _
.Executes(() =>
{
Information("已完成打包");
foreach (var file in Parameters.ArtifactOutput.GlobFiles("*.zip"))
{
var size = (new FileInfo(file).Length / 1024.0 / 1024.0).ToString("F3");
Information($"找到 Artifact{file}");
}
});
#endregion
#region
Target UseMaaChangeLog => _ => _
.Triggers(SetMaaChangeLog)
.Executes(() =>
{
_changeLog = $"对应 Commit[{Parameters.MainRepo}@{Parameters.CommitHash}](https://github.com/{Parameters.MainRepo}/commit/{Parameters.CommitHashFull})\n\n";
if (File.Exists(Parameters.MaaChangelogFile))
{
Information($"找到 {Parameters.MaaChangelogFile} 文件,读取内容作为更新日志");
var text = File.ReadAllText(Parameters.MaaChangelogFile);
_changeLog += text;
}
else
{
Warning($"未发现 {Parameters.MaaChangelogFile} 文件,将使用默认值");
}
});
Target UseMaaResourceChangeLog => _ => _
.Triggers(SetMaaChangeLog)
.Executes(() =>
{
_changeLog = $"对应 Commit[{Parameters.MainRepo}@{Parameters.CommitHash}](https://github.com/{Parameters.MainRepo}/commit/{Parameters.CommitHashFull})\n\n";
if (File.Exists(Parameters.MaaResourceChangeLogFile))
{
Information($"找到 {Parameters.MaaResourceChangeLogFile} 文件,读取内容作为更新日志");
var text = File.ReadAllText(Parameters.MaaResourceChangeLogFile);
_changeLog += text;
}
else
{
Warning($"未发现 {Parameters.MaaResourceChangeLogFile} 文件,将使用默认值");
}
});
Target SetMaaChangeLog => _ => _
.Executes(() =>
{
Information($"更新日志:\n================================\n{_changeLog}\n================================");
});
#endregion
#region
Target UsePublishArtifact => _ => _
.After(SetPackageBundled, SetMaaChangeLog)
.Produces(RootDirectory / "artifacts" / "*.zip")
.OnlyWhenStatic(() => GitHubActions != null);
Target UsePublishRelease => _ => _
.After(UsePublishArtifact)
.OnlyWhenStatic(() => GitHubActions != null)
.Executes(() =>
{
Information("在 GitHub Actions 中运行,执行发布 Release 任务");
Information($"当前 Actions{Parameters.GhActionName}");
if (Parameters.GhActionName == ActionConfiguration.DevBuild)
{
Information($"DevBuild 跳过发布 Release");
return;
}
GitHubTasks.GitHubClient = new GitHubClient(new ProductHeaderValue(nameof(NukeBuild)))
{
Credentials = new Credentials(Parameters.GitHubPersonalAccessToken)
};
if (Parameters.GhActionName == ActionConfiguration.ReleaseMaa)
{
Information($"运行 ReleaseMaa 将在 {Parameters.MainRepo} 创建 Release {Parameters.GhTag}");
CreateGitHubRelease(Parameters.MainRepo, Parameters.CommitHashFull, _version);
return;
}
if (Parameters.GhActionName == ActionConfiguration.ReleaseMaaCore)
{
Information($"运行 ReleaseMaaCore 将在 {Parameters.MainRepo} 创建 Release {Parameters.GhTag}");
CreateGitHubRelease(Parameters.MainRepo, Parameters.CommitHashFull, _version);
return;
}
if (Parameters.GhActionName == ActionConfiguration.ReleaseMaaResource)
{
Information($"运行 ReleaseMaaResource 将在 {Parameters.MaaResourceReleaseRepo} 创建 Release {_version}");
CreateGitHubRelease(Parameters.MaaResourceReleaseRepo, "main", _version);
return;
}
});
#endregion
#region Work Flow
/// <summary>
/// 见 <see cref="ActionConfiguration.DevBuild"/>
/// </summary>
Target DevBuild => _ => _
.DependsOn(Clean)
.DependsOn(UseCommitVersion)
.DependsOn(UseMaaDevBundle)
.DependsOn(UsePublishArtifact);
/// <summary>
/// 见 <see cref="ActionConfiguration.ReleaseMaa"/>
/// </summary>
Target ReleaseMaa => _ => _
.DependsOn(Clean)
.DependsOn(UseTagVersion)
.DependsOn(UseMaaLegacyBundle)
.DependsOn(UseMaaChangeLog)
.DependsOn(UsePublishArtifact)
.DependsOn(UsePublishRelease);
/// <summary>
/// 见 <see cref="ActionConfiguration.ReleaseMaaCore"/>
/// </summary>
Target ReleaseMaaCore => _ => _
.DependsOn(Clean)
.DependsOn(UseTagVersion)
.DependsOn(UseMaaCore)
.DependsOn(UseMaaChangeLog)
.DependsOn(UsePublishArtifact)
.DependsOn(UsePublishRelease);
/// <summary>
/// 见 <see cref="ActionConfiguration.ReleaseMaaResource"/>
/// </summary>
Target ReleaseMaaResource => _ => _
.DependsOn(Clean)
.DependsOn(UseCommitVersion)
.DependsOn(UseMaaResource)
.DependsOn(UseMaaResourceChangeLog)
.DependsOn(UsePublishArtifact)
.DependsOn(UsePublishRelease);
#endregion
#region Logger
private readonly Action<string> Information = (string msg) =>
{
Log.Information("{Message}", msg);
};
private readonly Action<string> Warning = (string msg) =>
{
Log.Warning("{Message}", msg);
};
#endregion
#region Utilities
private void BundlePackage(AbsolutePath input, string bundleName)
{
var packName = bundleName;
if (packName.EndsWith(".zip") is false)
{
packName += ".zip";
}
var bundle = Parameters.ArtifactOutput / packName;
Information($"编译输出:{input}");
Information($"打包输出:{bundle}");
Assert.True(Directory.Exists(input), $"输出目录 {input} 不存在");
Information($"创建压缩文件:{bundle}");
ZipFile.CreateFromDirectory(input, bundle, CompressionLevel.SmallestSize, false);
}
private void RemoveDebugSymbols(AbsolutePath outputDir)
{
var files = outputDir.GlobFiles("*.pdb", "*.lib", "*.exp", "*.exe.config");
foreach (var file in files)
{
FileSystemTasks.DeleteFile(file);
Information($"删除文件:{file}");
}
}
private void CreateGitHubRelease(string repo, string commitish, string releaseName)
{
var assets = Parameters.ArtifactOutput.GlobFiles("*.zip");
var release = new NewRelease(_version)
{
TargetCommitish = commitish,
Name = releaseName,
Body = _changeLog,
Draft = true,
Prerelease = false
};
var repoOwner = repo.Split('/')[0];
var repoName = repo.Split('/')[1];
var createdRelease = GitHubTasks.GitHubClient.Repository.Release.Create(repoOwner, repoName, release).Result;
Information($"创建 Release {Parameters.GhTag} 成功");
foreach (var asset in assets)
{
UploadReleaseAssetToGitHub(createdRelease, asset);
}
var _ = GitHubTasks.GitHubClient.Repository.Release.Edit(repoOwner, repoName, createdRelease.Id, new ReleaseUpdate { Draft = false }).Result;
}
private void UploadReleaseAssetToGitHub(Release release, AbsolutePath asset)
{
if (new FileExtensionContentTypeProvider().TryGetContentType(asset, out var assetContentType) is false)
{
assetContentType = "application/x-binary";
}
var releaseAssetUpload = new ReleaseAssetUpload
{
ContentType = assetContentType,
FileName = Path.GetFileName(asset),
RawData = File.OpenRead(path: asset)
};
try
{
var _ = GitHubTasks.GitHubClient.Repository.Release.UploadAsset(release, releaseAssetUpload).Result;
}
catch
{
Assert.Fail($"上传文件 {asset} 到 GitHub 失败");
}
Information($"上传文件 {asset} 到 GitHub 成功");
}
#endregion
}

View File

@@ -0,0 +1,31 @@
using Nuke.Common.Tooling;
using System.ComponentModel;
namespace MeoAssistantBuilder;
[TypeConverter(typeof(TypeConverter<BuildConfiguration>))]
public class BuildConfiguration : Enumeration
{
/// <summary>
/// MaaCore 在此配置下会自动复制资源文件,在直接发布 MaaBundle 时使用;
/// MaaWpf 在此配置下开启了优化,在直接发布 MaaBundle 时使用;
/// </summary>
public static readonly BuildConfiguration Release = new() { Value = nameof(Release) };
/// <summary>
/// MaaCore 在此配置下会生成完整的 Debug 信息,在 DevBuild 中使用;
/// MaaWpf 在此配置下关闭了优化,在 DevBuild 中使用;
/// </summary>
public static readonly BuildConfiguration RelWithDebInfo = new() { Value = nameof(RelWithDebInfo) };
/// <summary>
/// MaaCore 在此配置下不会自动复制资源文件,其余配置和 <see cref="Release"/> 相同,在发布 MaaCore 时使用
/// MaaWpf 在此配置下与 <see cref="Release"/> 相同,不建议使用
/// </summary>
public static readonly BuildConfiguration CICD = new() { Value = nameof(CICD) };
public static implicit operator string(BuildConfiguration configuration)
{
return configuration.Value;
}
}

View File

@@ -0,0 +1,238 @@
using Newtonsoft.Json.Linq;
using Nuke.Common;
using Nuke.Common.CI;
using Nuke.Common.CI.GitHubActions;
using Nuke.Common.IO;
using Nuke.Common.ProjectModel;
using Nuke.Common.Tools.Git;
using Serilog;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using static Nuke.Common.Tools.VSWhere.VSWhereTasks;
namespace MeoAssistantBuilder;
public partial class Build
{
#region Nuke
[Solution] readonly Solution Solution;
[CI] readonly GitHubActions GitHubActions;
#endregion
public class BuildParameters
{
#region
private static readonly Lazy<string> GetVsPath = new(() =>
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) is false)
{
return null;
}
var vsDirectory = VSWhere("-latest -nologo -property installationPath -format value -prerelease").FirstOrDefault().Text;
return string.IsNullOrWhiteSpace(vsDirectory) ? null : vsDirectory;
});
private readonly Func<string, string> GetMsBuildPath = (string vsPath) =>
{
var msBuildExe = Path.Combine(vsPath, @"MSBuild\Current\Bin\MSBuild.exe");
if (File.Exists(msBuildExe) is false)
{
msBuildExe = Path.Combine(vsPath, @"MSBuild\15.0\Bin\MSBuild.exe");
}
return File.Exists(msBuildExe) ? msBuildExe : null;
};
#endregion
// 工具链
public AbsolutePath MsBuildPath { get; }
public AbsolutePath VisualStudioPath { get; }
// 仓库
public bool IsFork { get; }
public string MainRepo { get; }
public string MasterBranchRef { get; }
public string DevBranchRef { get; }
public string ReleaseTagRefPrefix { get; }
public string MaaResourceReleaseRepo { get; }
// 路径
public AbsolutePath BuildOutput { get; }
public AbsolutePath ArtifactOutput { get; }
public AbsolutePath MaaChangelogFile { get; }
public AbsolutePath MaaResourceChangeLogFile { get; }
// 项目
public Project MaaCoreProject { get; }
public Project MaaWpfProject { get; }
// 配置
public string BuildTime { get; }
public string CommitHash { get; }
public string CommitHashFull { get; }
// CI
public bool IsGitHubActions { get; }
public bool IsPullRequest { get; }
public bool IsWorkflowDispatch { get; }
public string GitHubPersonalAccessToken { get; } = null;
public Dictionary<string, string> WorkflowDispatchArguments { get; }
public ActionConfiguration GhActionName { get; } = null;
public string GhBranch { get; } = null;
public string GhPullRequestId { get; } = null;
public string GhTag { get; } = null;
public BuildParameters(Build b)
{
// 工具链
var vs = GetVsPath.Value;
Assert.True(vs is not null, "找不到 Visual Studio");
VisualStudioPath = (AbsolutePath)vs;
var msbuild = GetMsBuildPath.Invoke(VisualStudioPath);
Assert.True(msbuild is not null, "找不到 MSBuild");
MsBuildPath = (AbsolutePath)msbuild;
// 仓库
IsFork = false;
MainRepo = "MaaAssistantArknights/MaaAssistantArknights";
MaaResourceReleaseRepo = "MaaAssistantArknights/MaaResourceRelease";
MasterBranchRef = "refs/heads/master";
DevBranchRef = "refs/heads/dev";
ReleaseTagRefPrefix = "refs/tags/v";
// 路径
BuildOutput = RootDirectory / "x64";
ArtifactOutput = RootDirectory / "artifacts";
MaaChangelogFile = RootDirectory / "CHANGELOG_MAA.md";
MaaResourceChangeLogFile = RootDirectory / "CHANGELOG_RES.md";
// 项目
MaaCoreProject = b.Solution.GetProject("MeoAssistant");
MaaWpfProject = b.Solution.GetProject("MeoAsstGui");
// 配置
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("zh-Hans-CN");
BuildTime = DateTimeOffset.UtcNow.ToLocalTime().ToString("yyyy-MM-dd-HH-mm-ss");
CommitHash = GitTasks.GitCurrentCommit();
Assert.True(CommitHash is not null, "Commit Hash 为 Null");
CommitHashFull = CommitHash;
CommitHash = CommitHash[..7];
// CI
IsGitHubActions = b.GitHubActions is not null;
if (IsGitHubActions)
{
GhActionName = (ActionConfiguration)b.GitHubActions.Workflow;
Assert.True(GhActionName is not null, $"GitHub Actions Workflow 名 {b.GitHubActions.Workflow} 无法转换为 ActionConfiguration");
Assert.False(string.IsNullOrEmpty(b.GitHubActions.Ref), "Ref 为 Null");
GitHubPersonalAccessToken = Environment.GetEnvironmentVariable("PUBLISH_GH_PAT");
if (b.GitHubActions.Ref.StartsWith(ReleaseTagRefPrefix))
{
var tag = $"v{b.GitHubActions.Ref.Replace(ReleaseTagRefPrefix, "")}";
var pattern = @"v((0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)";
var match = Regex.Match(tag, pattern);
if (match.Success)
{
GhTag = tag;
}
else
{
Assert.Fail("Tag 与 SemVer 不匹配");
}
}
else if (b.GitHubActions.Ref.StartsWith("refs/heads/"))
{
GhBranch = b.GitHubActions.Ref.Replace("refs/heads/", "");
}
else if (b.GitHubActions.IsPullRequest)
{
IsPullRequest = true;
GhPullRequestId = b.GitHubActions.Ref;
}
else
{
Log.Warning("未知的 Ref{Ref}", b.GitHubActions.Ref ?? "Null");
}
var ghEvent = b.GitHubActions.GitHubEvent;
if (ghEvent.ContainsKey("inputs"))
{
IsWorkflowDispatch = true;
WorkflowDispatchArguments = new();
var inputs = (JObject)ghEvent["inputs"];
foreach (var (k, v) in inputs)
{
WorkflowDispatchArguments.Add(k, v.ToString());
}
}
if (b.GitHubActions.BaseRef is not null)
{
// Fork
MainRepo = b.GitHubActions.Repository;
var repoOwner = MainRepo.Split("/")[0];
MaaResourceReleaseRepo = $"{repoOwner}/MaaResourceRelease";
IsFork = true;
}
// 若是 DevBuildBranch 必须不为 Master或者是 PR 至 Dev又或者是手动触发
if (GhActionName == ActionConfiguration.DevBuild)
{
if (IsWorkflowDispatch)
{
Assert.True(GhBranch is not null, "DevBuild -> Workflow DispatchBranch 为 Null");
}
else if (IsPullRequest)
{
Assert.True(GhPullRequestId is not null, "DevBuild -> Pull RequestPull Request Id 为 Null");
}
else
{
Assert.True(GhBranch != MasterBranch, "DevBuild -> Auto TriggeredBranch 为 master");
}
}
// 若是 ReleaseMaaTag 必须存在PAT 必须存在
if (GhActionName == ActionConfiguration.ReleaseMaa)
{
Assert.True(GhTag is not null, "ReleaseMaa -> Auto TriggeredTag 为 Null");
Assert.True(GitHubPersonalAccessToken is not null, "ReleaseMaa -> Auto TriggeredPAT 为 Null");
}
// 若是 ReleaseMaaCoreTag 必须存在PAT 必须存在
if (GhActionName == ActionConfiguration.ReleaseMaaCore)
{
Assert.True(GhTag is not null, "ReleaseMaaCore -> Auto TriggeredTag 为 Null");
Assert.True(GitHubPersonalAccessToken is not null, "ReleaseMaaCore -> Auto TriggeredPAT 为 Null");
}
// 若是 ReleaseMaaResourceBranch 必须为 MasterPAT 必须存在
if (GhActionName == ActionConfiguration.ReleaseMaaResource)
{
Assert.True(GhBranch == MasterBranch, "ReleaseMaaResource -> Auto TriggeredBranch 不为 master");
Assert.True(GitHubPersonalAccessToken is not null, "ReleaseMaaResource -> Auto TriggeredPAT 为 Null");
}
}
}
}
}

View File

@@ -1,23 +0,0 @@
namespace MeoAssistantBuilder;
public static class Constants
{
public const string MaaProjectRootDirectory = "../../";
public const string MaaProjectThirdPartyDirectory = "../../3rdparty";
public const string MaaProjectResourceDirectory = "../../resource";
public const string MaaProjectArtifactDirectory = "../../artifacts";
public const string MaaCoreProjectDirectory = "../MeoAssistant";
public const string MaaWpfProjectDirectory = "../MeoAsstGui";
public const string MaaCoreProjectFilePath = $"{MaaCoreProjectDirectory}/MeoAssistant.vcxproj";
public const string MaaWpfProjectFilePath = $"{MaaWpfProjectDirectory}/MeoAsstGui.csproj";
public const string MaaBuildOutputDirectory = $"{MaaProjectRootDirectory}/x64";
public const string MaaCoreBuildIntermediatOutputDirectory = $"{MaaCoreProjectDirectory}/x64";
public static readonly string[] MaaCoreExtraFiles = new[] { "MeoAssistant.pdb", "MeoAssistant.lib", "MeoAssistant.exp" };
public static readonly string[] MaaWpfExtraFiles = new[] { "MeoAsstGui.pdb", "MeoAsstGui.exe.config" };
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This file prevents unintended imports of unrelated MSBuild files -->
<!-- Uncomment to include parent Directory.Build.props file -->
<!--<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />-->
</Project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This file prevents unintended imports of unrelated MSBuild files -->
<!-- Uncomment to include parent Directory.Build.targets file -->
<!--<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />-->
</Project>

View File

@@ -1,17 +0,0 @@
using Cake.Common.Tools.MSBuild;
namespace MeoAssistantBuilder.Helper;
public static class BuildCoreHelper
{
public static void BuildCore(this MaaBuildContext context, string configuration)
{
context.MSBuild(Constants.MaaCoreProjectFilePath, new MSBuildSettings
{
WorkingDirectory = Constants.MaaProjectRootDirectory,
ToolPath = context.MsBuildExecutable,
Configuration = configuration,
PlatformTarget = PlatformTarget.x64
});
}
}

View File

@@ -1,18 +0,0 @@
using Cake.Common.Tools.MSBuild;
namespace MeoAssistantBuilder.Helper;
public static class BuildWpfHelper
{
public static void BuildWpf(this MaaBuildContext context, string configuration)
{
context.MSBuild(Constants.MaaWpfProjectFilePath, new MSBuildSettings
{
WorkingDirectory = Constants.MaaProjectRootDirectory,
Restore = true,
ToolPath = context.MsBuildExecutable,
Configuration = configuration,
PlatformTarget = PlatformTarget.x64
});
}
}

View File

@@ -1,61 +0,0 @@
using Cake.Common.Diagnostics;
using Cake.Common.IO;
namespace MeoAssistantBuilder.Helper;
public static class CleanDirectoryHelper
{
public static void CleanArtifacts(this MaaBuildContext context)
{
context.CleanDirectory(Constants.MaaProjectArtifactDirectory);
if (Directory.Exists(Constants.MaaProjectArtifactDirectory))
{
Directory.CreateDirectory(Constants.MaaProjectArtifactDirectory);
}
}
public static void CleanCore(this MaaBuildContext context)
{
context.CleanDirectory(Constants.MaaBuildOutputDirectory);
context.CleanDirectory(Constants.MaaCoreBuildIntermediatOutputDirectory);
}
public static void CleanWpf(this MaaBuildContext context)
{
context.CleanDirectory(Constants.MaaBuildOutputDirectory);
context.CleanDirectory(Path.Combine(Constants.MaaWpfProjectDirectory, "bin"));
context.CleanDirectory(Path.Combine(Constants.MaaWpfProjectDirectory, "obj"));
}
public static void CleanAll(this MaaBuildContext context)
{
CleanCore(context);
CleanWpf(context);
}
public static void RemoveExtraCore(this MaaBuildContext context, string buildOutput)
{
foreach (var extra in Constants.MaaCoreExtraFiles)
{
var file = Path.Combine(buildOutput, extra);
if (File.Exists(file))
{
File.Delete(file);
context.Information($"Delete file: {file}");
}
}
}
public static void RemoveExtraWpf(this MaaBuildContext context, string buildOutput)
{
foreach (var extra in Constants.MaaWpfExtraFiles)
{
var file = Path.Combine(buildOutput, extra);
if (File.Exists(file))
{
File.Delete(file);
context.Information($"Delete file: {file}");
}
}
}
}

View File

@@ -1,53 +0,0 @@
using Cake.Common.Diagnostics;
using Cake.Common.IO;
namespace MeoAssistantBuilder.Helper
{
public static class FileCopyHelper
{
public static void CopyThirdPartyDlls(this MaaBuildContext context, string outputDir)
{
var thirdPartyDlls = Directory.GetFiles(Path.Combine(Constants.MaaProjectThirdPartyDirectory, "bin"));
foreach (var f in thirdPartyDlls)
{
var fileName = new FileInfo(f).Name;
context.CopyFile(f, Path.Combine(outputDir, fileName));
context.Information($"Copy file: FROM {f} to {Path.Combine(outputDir, fileName)}");
}
}
public static void CopyResources(this MaaBuildContext context, string outputDir)
{
var resourceTargetDir = Path.Combine(outputDir, "resource");
var resourceThirdParty = Path.Combine(Constants.MaaProjectRootDirectory, "3rdparty\\resource");
var resoutceMaa = Path.Combine(Constants.MaaProjectRootDirectory, "resource");
var files = new List<string>();
var directories = new List<string>();
files.AddRange(Directory.GetFiles(resourceThirdParty));
files.AddRange(Directory.GetFiles(resoutceMaa));
directories.AddRange(Directory.GetDirectories(resourceThirdParty));
directories.AddRange(Directory.GetDirectories(resoutceMaa));
if (Directory.Exists(resourceTargetDir) is false)
{
Directory.CreateDirectory(resourceTargetDir);
}
foreach (var f in files)
{
var fileName = new FileInfo(f).Name;
context.CopyFile(f, Path.Combine(resourceTargetDir, fileName));
context.Information($"Copy file: FROM {f} TO {Path.Combine(resourceTargetDir, fileName)}");
}
foreach (var d in directories)
{
var dirName = new DirectoryInfo(d).Name;
context.CopyDirectory(d, Path.Combine(resourceTargetDir, dirName));
context.Information($"Copy directory: FROM {d} TO {Path.Combine(resourceTargetDir, dirName)}");
}
}
}
}

View File

@@ -1,38 +0,0 @@
using Cake.Common.Build;
using Cake.Common.Diagnostics;
namespace MeoAssistantBuilder.Helper;
public static class GetBuildInformationHelper
{
public static (string, string) GetBuildInformation(this MaaBuildContext context)
{
var ghAction = context.GitHubActions();
var buildTime = DateTime.UtcNow.AddHours(8);
var bt = buildTime.ToString("yyyy-MM-dd-HH-mm-ss");
string hash;
if (ghAction.IsRunningOnGitHubActions)
{
hash = ghAction.Environment.Workflow.Sha.ToLower()[..7];
}
else
{
try
{
var realPath = new DirectoryInfo(Constants.MaaProjectRootDirectory).FullName;
var repo = new LibGit2Sharp.Repository(realPath);
var commit = repo.Commits.First();
hash = commit.Sha.ToLower()[..7];
}
catch (Exception e)
{
context.Warning($"Failed to get git log: {e.Message}");
hash = "Unknown";
}
}
return (bt, hash);
}
}

View File

@@ -1,28 +0,0 @@
using Cake.Common;
using Cake.Common.Tools.VSWhere;
using Cake.Core;
using Cake.Core.Diagnostics;
using Cake.Frosting;
namespace MeoAssistantBuilder;
public class MaaBuildContext : FrostingContext
{
public string MsBuildExecutable { get; set; }
public MaaBuildContext(ICakeContext context) : base(context)
{
MsBuildExecutable = context.Argument<string>("tool", "");
if (MsBuildExecutable == "")
{
var vsPath = context.VSWhereLatest();
if (vsPath == null)
{
context.Log.Write(Verbosity.Normal, LogLevel.Fatal, "Can not find Visual Studio install path");
System.Environment.Exit(-1);
}
MsBuildExecutable = Path.Combine(vsPath.FullPath, "Msbuild\\Current\\Bin\\MsBuild.exe");
}
context.Log.Information($"Use MsBuild at: {MsBuildExecutable}");
}
}

View File

@@ -3,15 +3,36 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<Platforms>x64</Platforms>
<Configurations>Debug;Release;CICD</Configurations>
<RootNamespace></RootNamespace>
<NoWarn>CS0649;CS0169</NoWarn>
<NukeRootDirectory>..\..</NukeRootDirectory>
<NukeScriptDirectory>..\..</NukeScriptDirectory>
<NukeTelemetryVersion>1</NukeTelemetryVersion>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Cake.Frosting" Version="2.2.0" />
<PackageReference Include="LibGit2Sharp" Version="0.27.0-preview-0102" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Nuke.Common" Version="6.0.3" />
</ItemGroup>
<ItemGroup>
<PackageDownload Include="vswhere" Version="[3.0.2]" />
</ItemGroup>
<ItemGroup>
<None Remove="..\..\.github\workflows\release-maa-core.yml" />
</ItemGroup>
<ItemGroup>
<None Remove="..\..\.github\workflows\release-maa-core.yml" />
</ItemGroup>
<ItemGroup>
<None Remove="..\..\.github\workflows\release-maa-core.yml" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,26 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=HeapView_002EDelegateAllocation/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VariableHidesOuterVariable/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassNeverInstantiated_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBeMadeStatic_002ELocal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_INTERNAL_MODIFIER/@EntryValue">Implicit</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_PRIVATE_MODIFIER/@EntryValue">Implicit</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">ExpressionBody</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ThisQualifier/INSTANCE_MEMBERS_QUALIFY_MEMBERS/@EntryValue">0</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">False</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_ATTRIBUTE_LENGTH_FOR_SAME_LINE/@EntryValue">120</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">IF_OWNER_IS_SINGLE_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">WRAP_IF_LONG</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ANONYMOUSMETHOD_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -1,15 +0,0 @@
using Cake.Frosting;
using MeoAssistantBuilder;
var sArgs = new List<string>();
foreach (var arg in args)
{
var sa = arg.Split(" ")
.ToList();
sa.RemoveAll(x => string.IsNullOrEmpty(x) || string.IsNullOrWhiteSpace(x));
sArgs.AddRange(sa);
}
return new CakeHost()
.UseContext<MaaBuildContext>()
.Run(sArgs);

View File

@@ -0,0 +1,8 @@
{
"profiles": {
"MeoAssistantBuilder": {
"commandName": "Project",
"commandLineArgs": "--target DevTest"
}
}
}

View File

@@ -1,23 +1,54 @@
# MeoAssistantBuilder
MAA 构建项目
Maa 的构建工具
## 构建 MAA
## 使用方法
需要安装
- `.NET SDK 6.0`
- `.NET Framework SDK 4.8`
- `MSVC v143 x64`
- `MSBuild (VS2022)`
> 请先安装 `.NET SDK 6.0.x`
本项目根目录执行
```
dotnet run -- --target <TARGET>
解决方案根目录执行
``` sh
# PowerShell
./build.ps1 --target <TARGET>
# Command Prompt
./build.cmd --target <TARGET>
# Bash
./build.sh --target <TARGET>
```
TARGET 可选
`TARGET` 可选
- `DevBuild`
- `ReleaseCore`
- `ReleaseWpf`
- `ReleaseResource`
- DevBuild
- ReleaseMaa
- ReleaseMaaCore
- ReleaseMaaResource
关于各个 Target请参考 [ActionConfiguration.cs](./ActionConfiguration.cs)
关于 Target 之间的流程联系,请在安装 `Nuke.GlobalTool` 后再 Repo 的任意位置执行
``` sh
nuke --plan
```
构建生成的文件将会在 `./artifacts` 中
[可选] 安装 `Nuke.GlobalTool`
``` sh
dotnet tool install Nuke.GlobalTool --global
```
> 使用 `ZSH` 时,请将 `export PATH=$HOME/.dotnet/tools:$PATH` 添加到 `.zshrc`
关于 Nuke请参考 [官方文档](https://nuke.build/index.html)
## 注意事项
⚠ 每次执行时,会删除以下目录 ⚠
- ./artifacts
- ./x64/Release
- ./x64/RelWithDebInfo
- ./x64/CICD

View File

@@ -1,60 +0,0 @@
using Cake.Common.Diagnostics;
using Cake.Frosting;
using MeoAssistantBuilder.Helper;
using System.IO.Compression;
namespace MeoAssistantBuilder.Tasks;
[TaskName("DevBuild")]
public sealed class DevBuildTask : FrostingTask<MaaBuildContext>
{
public override void Run(MaaBuildContext context)
{
context.Information("--------------------------------------------------");
context.Information("1. Get build information");
context.Information("--------------------------------------------------");
context.CleanArtifacts();
var (bt, hash) = context.GetBuildInformation();
var artifact = $"MaaBundle-DevBuild-(CONF)-{hash}-{bt}.zip";
context.Information($"Dev build of commit {hash} at {bt}");
context.Information("--------------------------------------------------");
context.Information("2. Build MaaBundle with configuration Release");
context.Information("--------------------------------------------------");
context.CleanAll();
context.BuildCore("Release");
context.BuildWpf("Release");
var releaseOutput = Path.Combine(Constants.MaaBuildOutputDirectory, "Release");
var releaseArtifact = Path.Combine(Constants.MaaProjectArtifactDirectory, artifact.Replace("(CONF)", "Release"));
ZipFile.CreateFromDirectory(releaseOutput, releaseArtifact);
context.Information("--------------------------------------------------");
context.Information("3. Build MaaBundle with configuration RelWithDebInfo");
context.Information("--------------------------------------------------");
context.CleanAll();
context.BuildCore("RelWithDebInfo");
context.BuildWpf("RelWithDebInfo");
var releaseDebugOutput = Path.Combine(Constants.MaaBuildOutputDirectory, "RelWithDebInfo");
var releaseDebugArtifact = Path.Combine(Constants.MaaProjectArtifactDirectory, artifact.Replace("(CONF)", "RelWithDebInfo"));
ZipFile.CreateFromDirectory(releaseDebugOutput, releaseDebugArtifact);
context.Information("--------------------------------------------------");
context.Information("4. Build MaaBundle with configuration CICD");
context.Information("--------------------------------------------------");
context.CleanAll();
context.BuildCore("CICD");
context.BuildWpf("CICD");
var releaseCiOutput = Path.Combine(Constants.MaaBuildOutputDirectory, "CICD");
var releaseCiArtifact = Path.Combine(Constants.MaaProjectArtifactDirectory, artifact.Replace("(CONF)", "CICD"));
context.CopyThirdPartyDlls(releaseCiOutput);
context.CopyResources(releaseCiOutput);
ZipFile.CreateFromDirectory(releaseCiOutput, releaseCiArtifact);
}
}

View File

@@ -1,88 +0,0 @@
using Cake.Common.Build;
using Cake.Common.Diagnostics;
using Cake.Frosting;
using MeoAssistantBuilder.Helper;
using System.IO.Compression;
using System.Text.RegularExpressions;
namespace MeoAssistantBuilder.Tasks;
[TaskName("ReleaseBundle")]
public sealed class ReleaseBundleTask : FrostingTask<MaaBuildContext>
{
public override void Run(MaaBuildContext context)
{
context.Information("--------------------------------------------------");
context.Information("1. Read MaaCore version string");
context.Information("--------------------------------------------------");
var version = "UNKNOWN";
var versionFile = Path.Combine(Constants.MaaCoreProjectDirectory, "Version.h");
var versionFileContent = File.ReadAllText(versionFile);
var regex = @"v((0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)";
var match = Regex.Match(versionFileContent, regex);
if (match.Success is false)
{
context.Warning("Can not read version");
Environment.Exit(-1);
}
else
{
version = match.Value;
}
var ghActions = context.GitHubActions();
if (ghActions.IsRunningOnGitHubActions is false)
{
version += "-Local";
}
else
{
context.Information($"Running in GitHub Action with workflow name: {ghActions.Environment.Workflow.Workflow}, ref: {ghActions.Environment.Workflow.Ref}");
if (ghActions.Environment.Workflow.Workflow != "Release MaaCore")
{
version += $"-{ghActions.Environment.Workflow.RunId}";
}
else
{
var tag = ghActions.Environment.Workflow.Ref.Replace("refs/tags/", "");
if (tag != version)
{
context.Error($"Version do not match. From File: {version}. From Ref: {tag}");
Environment.Exit(-1);
}
}
}
context.Information($"MaaCore Version: {version}");
context.Information("--------------------------------------------------");
context.Information("2. Release MaaCore with third party dlls and resource");
context.Information("--------------------------------------------------");
var buildOutput = Path.Combine(Constants.MaaBuildOutputDirectory, "Release");
context.CleanAll();
context.CleanArtifacts();
context.BuildCore("Release");
context.RemoveExtraCore(buildOutput);
context.Information("--------------------------------------------------");
context.Information("3. Release MaaWpf with third party executables");
context.Information("--------------------------------------------------");
context.BuildWpf("Release");
context.RemoveExtraWpf(buildOutput);
context.Information("--------------------------------------------------");
context.Information("4. Bundle MaaBundle package");
context.Information("--------------------------------------------------");
var bundle = Path.Combine(Constants.MaaProjectArtifactDirectory, $"MaaBundle-{version}.zip");
ZipFile.CreateFromDirectory(buildOutput, bundle);
context.Information($"Bundled MaaBundle file: {bundle}");
}
}

View File

@@ -1,97 +0,0 @@
using Cake.Common.Build;
using Cake.Common.Diagnostics;
using Cake.Frosting;
using MeoAssistantBuilder.Helper;
using System.IO.Compression;
using System.Text.RegularExpressions;
namespace MeoAssistantBuilder.Tasks;
[TaskName("ReleaseCore")]
public sealed class ReleaseCoreTask : FrostingTask<MaaBuildContext>
{
public override void Run(MaaBuildContext context)
{
context.Information("--------------------------------------------------");
context.Information("1. Read MaaCore version string");
context.Information("--------------------------------------------------");
var version = "UNKNOWN";
var versionFile = Path.Combine(Constants.MaaCoreProjectDirectory, "Version.h");
var versionFileContent = File.ReadAllText(versionFile);
var regex = @"v((0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)";
var match = Regex.Match(versionFileContent, regex);
if (match.Success is false)
{
context.Warning("Can not read version");
Environment.Exit(-1);
}
else
{
version = match.Value;
}
var ghActions = context.GitHubActions();
if (ghActions.IsRunningOnGitHubActions is false)
{
version += "-Local";
}
else
{
context.Information($"Running in GitHub Action with workflow name: {ghActions.Environment.Workflow.Workflow}, ref: {ghActions.Environment.Workflow.Ref}");
if (ghActions.Environment.Workflow.Workflow != "Release MaaCore")
{
version += $"-{ghActions.Environment.Workflow.RunId}";
}
else
{
var tag = ghActions.Environment.Workflow.Ref.Replace("refs/tags/", "");
if (tag != version)
{
context.Error($"Version do not match. From File: {version}. From Ref: {tag}");
Environment.Exit(-1);
}
}
}
context.Information($"MaaCore Version: {version}");
context.Information("--------------------------------------------------");
context.Information("2. Build MaaCore with CICD configuration");
context.Information("--------------------------------------------------");
context.CleanCore();
context.CleanArtifacts();
context.BuildCore("CICD");
context.Information("--------------------------------------------------");
context.Information("3. Remove extra files from build output");
context.Information("--------------------------------------------------");
var buildOutput = Path.Combine(Constants.MaaBuildOutputDirectory, "CICD");
context.RemoveExtraCore(buildOutput);
context.Information("--------------------------------------------------");
context.Information("4. Copy 3rd party library DLLs to output directory");
context.Information("--------------------------------------------------");
var thirdPartyDlls = Directory.GetFiles(Path.Combine(Constants.MaaProjectThirdPartyDirectory, "bin"));
foreach (var f in thirdPartyDlls)
{
var fileName = new FileInfo(f).Name;
File.Copy(f, Path.Combine(buildOutput, fileName));
context.Information($"Copy file: FROM {f} to {Path.Combine(buildOutput, fileName)}");
}
context.Information("--------------------------------------------------");
context.Information("5. Bundle MaaCore DLLs");
context.Information("--------------------------------------------------");
var bundle = Path.Combine(Constants.MaaProjectArtifactDirectory, $"MaaCore-{version}.zip");
ZipFile.CreateFromDirectory(buildOutput, bundle);
context.Information($"Bundled MAACore DLLs file: {bundle}");
}
}

View File

@@ -1,42 +0,0 @@
using Cake.Common.Diagnostics;
using Cake.Frosting;
using MeoAssistantBuilder.Helper;
using System.IO.Compression;
namespace MeoAssistantBuilder.Tasks;
[TaskName("ReleaseResource")]
public sealed class ReleaseResourceTask : FrostingTask<MaaBuildContext>
{
public override void Run(MaaBuildContext context)
{
context.Information("--------------------------------------------------");
context.Information("1. Get bundle information");
context.Information("--------------------------------------------------");
var (hash, bt) = context.GetBuildInformation();
context.CleanArtifacts();
var version = $"{hash}-{bt}";
context.Information($"MaaResource Version: {version}");
context.Information("--------------------------------------------------");
context.Information("2. Copy MaaResource");
context.Information("--------------------------------------------------");
var resDir = Path.Combine(Constants.MaaBuildOutputDirectory, "resource");
if (Directory.Exists(resDir) is false)
{
Directory.CreateDirectory(resDir);
}
context.CopyResources(resDir);
context.Information("--------------------------------------------------");
context.Information("3. Bundle MaaResource");
context.Information("--------------------------------------------------");
var bundle = Path.Combine(Constants.MaaProjectArtifactDirectory, $"MaaResource-{version}.zip");
ZipFile.CreateFromDirectory(resDir, bundle);
context.Information($"Bundled MaaResource file: {bundle}");
}
}

View File

@@ -1,45 +0,0 @@
using Cake.Common.Diagnostics;
using Cake.Frosting;
using MeoAssistantBuilder.Helper;
using System.IO.Compression;
namespace MeoAssistantBuilder.Tasks;
[TaskName("ReleaseWpf")]
public sealed class ReleaseWpfTask : FrostingTask<MaaBuildContext>
{
public override void Run(MaaBuildContext context)
{
context.Information("--------------------------------------------------");
context.Information("1. Build MaaWpf with ReleaseCore configuration");
context.Information("--------------------------------------------------");
var (hash, bt) = context.GetBuildInformation();
context.CleanWpf();
context.CleanArtifacts();
context.BuildWpf("CICD");
context.Information("--------------------------------------------------");
context.Information("2. Remove extra files from build output");
context.Information("--------------------------------------------------");
var buildOutput = Path.Combine(Constants.MaaBuildOutputDirectory, "CICD");
context.RemoveExtraWpf(buildOutput);
context.Information("--------------------------------------------------");
context.Information("3. Set MaaWpf Version Number");
context.Information("--------------------------------------------------");
var version = $"{hash}-{bt}";
context.Information($"MaaWpf Version: {version}");
context.Information("--------------------------------------------------");
context.Information("4. Bundle MaaWpf Executables");
context.Information("--------------------------------------------------");
var bundle = Path.Combine(Constants.MaaProjectArtifactDirectory, $"MaaWpf-{version}.zip");
ZipFile.CreateFromDirectory(buildOutput, bundle);
context.Information($"Bundled MaaWpf Executables file: {bundle}");
}
}

View File

@@ -1,29 +1,29 @@
<Application x:Class="MeoAsstGui.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MeoAsstGui"
xmlns:s="https://github.com/canton7/Stylet">
<Application.Resources>
<!--<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.Blue.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Indigo.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>-->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<s:ApplicationLoader>
<s:ApplicationLoader.Bootstrapper>
<local:Bootstrapper />
</s:ApplicationLoader.Bootstrapper>
</s:ApplicationLoader>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" />
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" />
<ResourceDictionary Source="Resources/Icon.xaml" />
<ResourceDictionary Source="Resources/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
<Application x:Class="MeoAsstGui.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MeoAsstGui"
xmlns:s="https://github.com/canton7/Stylet">
<Application.Resources>
<!--<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.Blue.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Indigo.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>-->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<s:ApplicationLoader>
<s:ApplicationLoader.Bootstrapper>
<local:Bootstrapper />
</s:ApplicationLoader.Bootstrapper>
</s:ApplicationLoader>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" />
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" />
<ResourceDictionary Source="Resources/Icon.xaml" />
<ResourceDictionary Source="Resources/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -16,6 +16,7 @@ using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -311,6 +312,34 @@ namespace MeoAsstGui
{
return false;
}
// 开发版、测试版不检查更新
// 正式版vX.X.X
// DevBuild (CI)yyyy-MM-dd-HH-mm-ss-{CommitHash[..7]}
// DevBuild (Local)yyyy-MM-dd-HH-mm-ss-{CommitHash[..7]}-Local
// Release (Local Commit)v.{CommitHash[..7]}-Local
// Release (Local Tag){Tag}-Local
// Debug (Local)DEBUG VERSION
// Script Compiledc{CommitHash[..7]}
if (_curVersion == "DEBUG VERSION")
{
return false;
}
if (_curVersion.StartsWith("c"))
{
return false;
}
if (_curVersion.Contains("Local"))
{
return false;
}
var pattern = @"v((0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)";
var match = Regex.Match(_curVersion, pattern);
if (match.Success is false)
{
return false;
}
const int requestRetryMaxTimes = 5;
var response = RequestApi(RequestUrl);
for (int i = 0; response.Length == 0 && i >= requestRetryMaxTimes; i++)

2
src/dart/.gitignore vendored
View File

@@ -3,11 +3,13 @@
*.log
*.pyc
*.swp
*.exp
.DS_Store
.atom/
.buildlog/
.history
.svn/
cmake-build-debug/
# IntelliJ related
*.iml

View File

@@ -15,6 +15,7 @@
*.iws
.idea/
resource/
runtime/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line

View File

@@ -1,10 +1,33 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
# This file should be version controlled.
version:
revision: 5464c5bac742001448fe4fc0597be939379f88ea
revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
- platform: linux
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
- platform: windows
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@@ -62,7 +62,7 @@ class _MyHomePageState extends State<MyHomePage> {
@override
void initState() {
setState(() {
_asst = MaaCore(p.current, callback);
_asst = MaaCore(p.join(p.current), callback);
_asst.connect('adb', 'emulator-5554');
});
super.initState();
@@ -95,4 +95,5 @@ class _MyHomePageState extends State<MyHomePage> {
),
));
}
}

View File

@@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
maa_core
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
@@ -14,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View File

@@ -42,7 +42,7 @@ packages:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
version: "1.16.0"
cupertino_icons:
dependency: "direct main"
description:
@@ -56,7 +56,7 @@ packages:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.0"
ffi:
dependency: "direct main"
description:
@@ -108,7 +108,7 @@ packages:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
version: "0.1.4"
meta:
dependency: transitive
description:
@@ -122,7 +122,7 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
version: "1.8.1"
sky_engine:
dependency: transitive
description: flutter
@@ -134,7 +134,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
version: "1.8.2"
stack_trace:
dependency: transitive
description:
@@ -169,21 +169,14 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.8"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "0.4.9"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
version: "2.1.2"
sdks:
dart: ">=2.16.2 <3.0.0"
dart: ">=2.17.0-0 <3.0.0"
flutter: ">=2.5.0"

17
src/dart/example/windows/.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
flutter/ephemeral/
# Visual Studio user-specific files.
*.suo
*.user
*.userosscache
*.sln.docstates
# Visual Studio build-related files.
x64/
x86/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

View File

@@ -0,0 +1,101 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.14)
project(example LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "example")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Define build configuration option.
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(IS_MULTICONFIG)
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
endif()
# Define settings for the Profile build mode.
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
# Use Unicode for all projects.
add_definitions(-DUNICODE -D_UNICODE)
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
target_compile_options(${TARGET} PRIVATE /EHsc)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# Support files are copied into place next to the executable, so that it can
# run in place. This is done instead of making a separate bundle (as on Linux)
# so that building and running from within Visual Studio will work.
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
# Make the "install" step default, as it's required to run.
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
CONFIGURATIONS Profile;Release
COMPONENT Runtime)

View File

@@ -0,0 +1,104 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.14)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_windows.h"
"flutter_messenger.h"
"flutter_plugin_registrar.h"
"flutter_texture_registrar.h"
)
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"core_implementations.cc"
"standard_codec.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc"
"flutter_view_controller.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_app flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
${PHONY_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
windows-x64 $<CONFIG>
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)

View File

@@ -0,0 +1,11 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void RegisterPlugins(flutter::PluginRegistry* registry) {
}

View File

@@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View File

@@ -0,0 +1,23 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View File

@@ -0,0 +1,32 @@
cmake_minimum_required(VERSION 3.14)
project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp"
"main.cpp"
"utils.cpp"
"win32_window.cpp"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Disable Windows macros that collide with C++ standard library functions.
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
# Add dependency libraries and include directories. Add any application-specific
# dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)

View File

@@ -0,0 +1,121 @@
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APP_ICON ICON "resources\\app_icon.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
#ifdef FLUTTER_BUILD_NUMBER
#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
#else
#define VERSION_AS_NUMBER 1,0,0
#endif
#ifdef FLUTTER_BUILD_NAME
#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
#else
#define VERSION_AS_STRING "1.0.0"
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_AS_NUMBER
PRODUCTVERSION VERSION_AS_NUMBER
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.example" "\0"
VALUE "FileDescription", "example" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "example" "\0"
VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0"
VALUE "OriginalFilename", "example.exe" "\0"
VALUE "ProductName", "example" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@@ -0,0 +1,61 @@
#include "flutter_window.h"
#include <optional>
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
FlutterWindow::~FlutterWindow() {}
bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) {
return false;
}
RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false;
}
RegisterPlugins(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
return true;
}
void FlutterWindow::OnDestroy() {
if (flutter_controller_) {
flutter_controller_ = nullptr;
}
Win32Window::OnDestroy();
}
LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
// Give Flutter, including plugins, an opportunity to handle window messages.
if (flutter_controller_) {
std::optional<LRESULT> result =
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
lparam);
if (result) {
return *result;
}
}
switch (message) {
case WM_FONTCHANGE:
flutter_controller_->engine()->ReloadSystemFonts();
break;
}
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}

View File

@@ -0,0 +1,33 @@
#ifndef RUNNER_FLUTTER_WINDOW_H_
#define RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <memory>
#include "win32_window.h"
// A window that does nothing but host a Flutter view.
class FlutterWindow : public Win32Window {
public:
// Creates a new FlutterWindow hosting a Flutter view running |project|.
explicit FlutterWindow(const flutter::DartProject& project);
virtual ~FlutterWindow();
protected:
// Win32Window:
bool OnCreate() override;
void OnDestroy() override;
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
LPARAM const lparam) noexcept override;
private:
// The project to run.
flutter::DartProject project_;
// The Flutter instance hosted by this window.
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
};
#endif // RUNNER_FLUTTER_WINDOW_H_

View File

@@ -0,0 +1,43 @@
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include "flutter_window.h"
#include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) {
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
CreateAndAttachConsole();
}
// Initialize COM, so that it is available for use in the library and/or
// plugins.
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
flutter::DartProject project(L"data");
std::vector<std::string> command_line_arguments =
GetCommandLineArguments();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"example", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);
::MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
::CoUninitialize();
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,16 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Runner.rc
//
#define IDI_APP_ICON 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
</assembly>

View File

@@ -0,0 +1,64 @@
#include "utils.h"
#include <flutter_windows.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
void CreateAndAttachConsole() {
if (::AllocConsole()) {
FILE *unused;
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
_dup2(_fileno(stdout), 1);
}
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
_dup2(_fileno(stdout), 2);
}
std::ios::sync_with_stdio();
FlutterDesktopResyncOutputStreams();
}
}
std::vector<std::string> GetCommandLineArguments() {
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
int argc;
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
if (argv == nullptr) {
return std::vector<std::string>();
}
std::vector<std::string> command_line_arguments;
// Skip the first argument as it's the binary name.
for (int i = 1; i < argc; i++) {
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
}
::LocalFree(argv);
return command_line_arguments;
}
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
if (utf16_string == nullptr) {
return std::string();
}
int target_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, nullptr, 0, nullptr, nullptr);
std::string utf8_string;
if (target_length == 0 || target_length > utf8_string.max_size()) {
return utf8_string;
}
utf8_string.resize(target_length);
int converted_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, utf8_string.data(),
target_length, nullptr, nullptr);
if (converted_length == 0) {
return std::string();
}
return utf8_string;
}

View File

@@ -0,0 +1,19 @@
#ifndef RUNNER_UTILS_H_
#define RUNNER_UTILS_H_
#include <string>
#include <vector>
// Creates a console for the process, and redirects stdout and stderr to
// it for both the runner and the Flutter library.
void CreateAndAttachConsole();
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
// encoded in UTF-8. Returns an empty std::string on failure.
std::string Utf8FromUtf16(const wchar_t* utf16_string);
// Gets the command line arguments passed in as a std::vector<std::string>,
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
std::vector<std::string> GetCommandLineArguments();
#endif // RUNNER_UTILS_H_

View File

@@ -0,0 +1,245 @@
#include "win32_window.h"
#include <flutter_windows.h>
#include "resource.h"
namespace {
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
// The number of Win32Window objects that currently exist.
static int g_active_window_count = 0;
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
// Scale helper to convert logical scaler values to physical using passed in
// scale factor
int Scale(int source, double scale_factor) {
return static_cast<int>(source * scale_factor);
}
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
// This API is only needed for PerMonitor V1 awareness mode.
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
HMODULE user32_module = LoadLibraryA("User32.dll");
if (!user32_module) {
return;
}
auto enable_non_client_dpi_scaling =
reinterpret_cast<EnableNonClientDpiScaling*>(
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
if (enable_non_client_dpi_scaling != nullptr) {
enable_non_client_dpi_scaling(hwnd);
FreeLibrary(user32_module);
}
}
} // namespace
// Manages the Win32Window's window class registration.
class WindowClassRegistrar {
public:
~WindowClassRegistrar() = default;
// Returns the singleton registar instance.
static WindowClassRegistrar* GetInstance() {
if (!instance_) {
instance_ = new WindowClassRegistrar();
}
return instance_;
}
// Returns the name of the window class, registering the class if it hasn't
// previously been registered.
const wchar_t* GetWindowClass();
// Unregisters the window class. Should only be called if there are no
// instances of the window.
void UnregisterWindowClass();
private:
WindowClassRegistrar() = default;
static WindowClassRegistrar* instance_;
bool class_registered_ = false;
};
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
const wchar_t* WindowClassRegistrar::GetWindowClass() {
if (!class_registered_) {
WNDCLASS window_class{};
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.lpszClassName = kWindowClassName;
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = GetModuleHandle(nullptr);
window_class.hIcon =
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
window_class.hbrBackground = 0;
window_class.lpszMenuName = nullptr;
window_class.lpfnWndProc = Win32Window::WndProc;
RegisterClass(&window_class);
class_registered_ = true;
}
return kWindowClassName;
}
void WindowClassRegistrar::UnregisterWindowClass() {
UnregisterClass(kWindowClassName, nullptr);
class_registered_ = false;
}
Win32Window::Win32Window() {
++g_active_window_count;
}
Win32Window::~Win32Window() {
--g_active_window_count;
Destroy();
}
bool Win32Window::CreateAndShow(const std::wstring& title,
const Point& origin,
const Size& size) {
Destroy();
const wchar_t* window_class =
WindowClassRegistrar::GetInstance()->GetWindowClass();
const POINT target_point = {static_cast<LONG>(origin.x),
static_cast<LONG>(origin.y)};
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
double scale_factor = dpi / 96.0;
HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this);
if (!window) {
return false;
}
return OnCreate();
}
// static
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
if (message == WM_NCCREATE) {
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
SetWindowLongPtr(window, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
EnableFullDpiSupportIfAvailable(window);
that->window_handle_ = window;
} else if (Win32Window* that = GetThisFromHandle(window)) {
return that->MessageHandler(window, message, wparam, lparam);
}
return DefWindowProc(window, message, wparam, lparam);
}
LRESULT
Win32Window::MessageHandler(HWND hwnd,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
switch (message) {
case WM_DESTROY:
window_handle_ = nullptr;
Destroy();
if (quit_on_close_) {
PostQuitMessage(0);
}
return 0;
case WM_DPICHANGED: {
auto newRectSize = reinterpret_cast<RECT*>(lparam);
LONG newWidth = newRectSize->right - newRectSize->left;
LONG newHeight = newRectSize->bottom - newRectSize->top;
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
case WM_SIZE: {
RECT rect = GetClientArea();
if (child_content_ != nullptr) {
// Size and position the child window.
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, TRUE);
}
return 0;
}
case WM_ACTIVATE:
if (child_content_ != nullptr) {
SetFocus(child_content_);
}
return 0;
}
return DefWindowProc(window_handle_, message, wparam, lparam);
}
void Win32Window::Destroy() {
OnDestroy();
if (window_handle_) {
DestroyWindow(window_handle_);
window_handle_ = nullptr;
}
if (g_active_window_count == 0) {
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
}
}
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
return reinterpret_cast<Win32Window*>(
GetWindowLongPtr(window, GWLP_USERDATA));
}
void Win32Window::SetChildContent(HWND content) {
child_content_ = content;
SetParent(content, window_handle_);
RECT frame = GetClientArea();
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true);
SetFocus(child_content_);
}
RECT Win32Window::GetClientArea() {
RECT frame;
GetClientRect(window_handle_, &frame);
return frame;
}
HWND Win32Window::GetHandle() {
return window_handle_;
}
void Win32Window::SetQuitOnClose(bool quit_on_close) {
quit_on_close_ = quit_on_close;
}
bool Win32Window::OnCreate() {
// No-op; provided for subclasses.
return true;
}
void Win32Window::OnDestroy() {
// No-op; provided for subclasses.
}

View File

@@ -0,0 +1,98 @@
#ifndef RUNNER_WIN32_WINDOW_H_
#define RUNNER_WIN32_WINDOW_H_
#include <windows.h>
#include <functional>
#include <memory>
#include <string>
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
// inherited from by classes that wish to specialize with custom
// rendering and input handling
class Win32Window {
public:
struct Point {
unsigned int x;
unsigned int y;
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
};
struct Size {
unsigned int width;
unsigned int height;
Size(unsigned int width, unsigned int height)
: width(width), height(height) {}
};
Win32Window();
virtual ~Win32Window();
// Creates and shows a win32 window with |title| and position and size using
// |origin| and |size|. New windows are created on the default monitor. Window
// sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size to will treat the width height passed in to this function
// as logical pixels and scale to appropriate for the default monitor. Returns
// true if the window was created successfully.
bool CreateAndShow(const std::wstring& title,
const Point& origin,
const Size& size);
// Release OS resources associated with window.
void Destroy();
// Inserts |content| into the window tree.
void SetChildContent(HWND content);
// Returns the backing Window handle to enable clients to set icon and other
// window properties. Returns nullptr if the window has been destroyed.
HWND GetHandle();
// If true, closing this window will quit the application.
void SetQuitOnClose(bool quit_on_close);
// Return a RECT representing the bounds of the current client area.
RECT GetClientArea();
protected:
// Processes and route salient window messages for mouse handling,
// size change and DPI. Delegates handling of these to member overloads that
// inheriting classes can handle.
virtual LRESULT MessageHandler(HWND window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Called when CreateAndShow is called, allowing subclass window-related
// setup. Subclasses should return false if setup fails.
virtual bool OnCreate();
// Called when Destroy is called.
virtual void OnDestroy();
private:
friend class WindowClassRegistrar;
// OS callback called by message pump. Handles the WM_NCCREATE message which
// is passed when the non-client area is being created and enables automatic
// non-client DPI scaling so that the non-client area automatically
// responsponds to changes in DPI. All other messages are handled by
// MessageHandler.
static LRESULT CALLBACK WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Retrieves a class instance pointer for |window|
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
bool quit_on_close_ = false;
// window handle for top level window.
HWND window_handle_ = nullptr;
// window handle for hosted content.
HWND child_content_ = nullptr;
};
#endif // RUNNER_WIN32_WINDOW_H_

View File

@@ -1,85 +1,150 @@
import 'dart:async';
import 'dart:ffi';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:ffi/ffi.dart';
import 'package:maa_core/typedefs.dart';
import 'package:path/path.dart' as p;
class MaaCore implements MaaCoreInterface {
late Assistant _asst;
late AsstLoadResourceFunc _asstLoadResource;
late AsstCreateFunc _asstCreate;
late AsstCreateExFunc _asstCreateEx;
late AsstConnectFunc _asstConnect;
late AsstDestroyFunc _asstDestroy;
late AsstAppendTaskFunc _asstAppendTask;
late AsstSetTaskParamsFunc _asstSetTaskParams;
late AsstStartFunc _asstStart;
late AsstStopFunc _asstStop;
late AsstCtrlerClickFunc _asstCtrlerClick;
late AsstGetVersionFunc _asstGetVersion;
late AsstLogFunc _asstLog;
// Native Symbols
// MeoAssistant
static late AsstLoadResourceFunc _asstLoadResource;
static late AsstCreateExFunc _asstCreateEx;
static late AsstConnectFunc _asstConnect;
static late AsstDestroyFunc _asstDestroy;
static late AsstAppendTaskFunc _asstAppendTask;
static late AsstSetTaskParamsFunc _asstSetTaskParams;
static late AsstStartFunc _asstStart;
static late AsstStopFunc _asstStop;
static late AsstCtrlerClickFunc _asstCtrlerClick;
static late AsstGetVersionFunc _asstGetVersion;
static late AsstLogFunc _asstLog;
// callbacklib
static late InitDartApiFunc _initDartApi;
static late Pointer<AsstCallbackNative> _asstCallback;
static late NativeFreeFunc _nativeFree;
static bool _resourceLoaded = false;
late ReceivePort? _receivePort;
static bool _apiInited = false;
ReceivePort? get receivePort => _receivePort;
static bool _symbolsLoaded = false;
late ReceivePort? _receivePort;
late Assistant _asst;
late StreamSubscription _portSubscription;
late List<Pointer> _allocated;
static Future<String> get platformVersion async {
return Future<String>(() => '0.0.1');
}
void _loadResource(String path) {
final strPtr = toManagedString(path);
final result = _asstLoadResource(strPtr);
_resourceLoaded = _resourceLoaded || result;
}
MaaCore(String libDir, [Function(String)? callback]) {
_loadCppLib(libDir);
if (!_apiInited) {
final callbackLib = DynamicLibrary.open(p.join(libDir, 'libCallback.so'));
final InitDartApiFunc initNativeApi =
callbackLib.lookup<InitDartApiNative>('init_dart_api').asFunction();
initNativeApi(NativeApi.initializeApiDLData);
_apiInited = true;
ReceivePort? get receivePort => _receivePort;
static List<String> get _deps {
if (Platform.isWindows){
return [
'libiomp5md',
'mklml',
'mkldnn',
'opencv_world453',
'paddle_inference',
'ppocr',
];
}
return [];
}
static String get meoAssistantLibName {
if (Platform.isLinux) {
return 'libMeoAssistant.so';
} else if (Platform.isWindows) {
return 'MeoAssistant.dll';
}
return 'libMeoAssistant.dylib';
}
static String get callbackLibName {
if (Platform.isLinux) {
return 'libCallback.so';
} else if (Platform.isWindows) {
return 'Callback.dll';
}
return 'libCallback.dylib';
}
static _loadResource(String dir) {
print("init resource");
final dirPtr = dir.toNativeUtf8();
_asstLoadResource(dirPtr);
malloc.free(dirPtr);
_resourceLoaded = true;
}
MaaCore(String libDir, [Function(String)? callback]) {
init(libDir);
_allocated = [];
if (!_resourceLoaded) {
_loadResource(libDir);
}
_receivePort = ReceivePort();
final nativePort = _receivePort!.sendPort.nativePort;
final portPtr = malloc.allocate<Int64>(sizeOf<Int64>());
_asst = _asstCreateEx(nativeCallback, portPtr.cast<Void>());
_asst = _asstCreateEx(_asstCallback, portPtr.cast<Void>());
_allocated.add(portPtr);
portPtr.value = nativePort;
if (callback != null) {
_portSubscription = _receivePort!.listen(_wrapCallback(callback));
}
}
static void init(String libDir, {bool reloadResource = false}) {
if (!_symbolsLoaded) {
_loadNativeSymbols(libDir);
}
if (!_apiInited) {
_initDartApi(NativeApi.initializeApiDLData);
_apiInited = true;
}
if (!_resourceLoaded || reloadResource) {
_loadResource(libDir);
}
}
void Function(dynamic) _wrapCallback(void Function(String) cb) {
return (dynamic data) {
// c will manage the memory for the string
String msg = data;
final Pointer<Utf8> ptr = Pointer.fromAddress(data as int);
String msg = ptr.toDartString();
print("msg from ptr ($ptr): $msg before free");
_nativeFree(ptr.cast<Void>());
cb(msg);
};
}
Pointer<AsstCallbackNative> get nativeCallback {
return DynamicLibrary.open('./libCallback.so')
.lookup<AsstCallbackNative>('callback');
static void _loadNativeSymbols(String libDir) {
_loadMeoAssistant(libDir);
_loadCallbackLib(libDir);
_symbolsLoaded = true;
}
void _loadCppLib(String libDir) {
final lib = DynamicLibrary.open(p.join(libDir, 'libMeoAssistant.so'));
static void _loadCallbackLib(String libDir) {
final lib = DynamicLibrary.open(p.join(libDir, callbackLibName));
_initDartApi = lib.lookup<InitDartApiNative>('init_dart_api').asFunction();
_nativeFree = lib.lookup<NativeFreeNative>('native_free').asFunction();
_asstCallback = lib.lookup<AsstCallbackNative>('callback');
}
static void _loadMeoAssistant(String libDir) {
if (Platform.isWindows) {
for (var dep in _deps) {
print("load dep: $dep");
DynamicLibrary.open(p.join(libDir, dep+'.dll'));
print("loaded dep: $dep");
}
}
final lib = DynamicLibrary.open(p.join(libDir, meoAssistantLibName));
_asstLoadResource =
lib.lookup<AsstLoadResourceNative>('AsstLoadResource').asFunction();
_asstCreate = lib.lookup<AsstCreateNative>('AsstCreate').asFunction();
_asstCreateEx = lib.lookup<AsstCreateExNative>('AsstCreateEx').asFunction();
_asstConnect = lib.lookup<AsstConnectNative>('AsstConnect').asFunction();
_asstDestroy = lib.lookup<AsstDestroyNative>('AsstDestroy').asFunction();
@@ -171,4 +236,10 @@ class MaaCore implements MaaCoreInterface {
_allocated.add(ptr);
return ptr;
}
static String get version {
final ptr = _asstGetVersion();
final str = ptr.toDartString();
return str;
}
}

View File

@@ -72,6 +72,9 @@ typedef InitDartApiNative = NativeFunction<Void Function(Pointer<Void>)>;
typedef CleanUpDartVMFunc = void Function();
typedef CleanUpDartVMNative = NativeFunction<Void Function()>;
typedef NativeFreeFunc = void Function(Pointer<Void>);
typedef NativeFreeNative = NativeFunction<Void Function(Pointer<Void>)>;
abstract class MaaCoreInterface {
void destroy();
bool connect(String adbPath, String address,[String config = '']);

View File

@@ -0,0 +1,15 @@
{
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": ""
}
]
}

View File

@@ -1,16 +1,23 @@
#include "dart_api_dl.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
void callback(int, const char *, void *);
void init_dart_api(void *);
void init_dart_api(void *api_data) {
Dart_InitializeApiDL(api_data);
}
void native_free(void *ptr) {
free(ptr);
}
void callback(int retval, const char *json_str, void *custom_arg) {
Dart_Port port = *(Dart_Port *)custom_arg;
int n_digits = 1;
int k = retval;
while (k > 10) {
@@ -18,15 +25,14 @@ void callback(int retval, const char *json_str, void *custom_arg) {
k /= 10;
}
Dart_CObject obj;
int required_len = n_digits + strlen(json_str) + 1;
char retstr[required_len];
retstr[required_len-1] = 0;
sprintf(retstr, "{\"retval\": %d, \"payload\": %s }", retval, json_str);
int required_len = n_digits + strlen(json_str) + 30;
char* retptr = calloc(required_len, sizeof(char));
snprintf(retptr, required_len, "{\"retval\":%d,\"payload\":%s}", retval, json_str);
obj.type = Dart_CObject_kString;
obj.value.as_string = retstr;
printf("C: Sending Pointer %ld to dart through port %ld\n", (int64_t)json_str, port);
obj.value.as_int64 = (uintptr_t)retptr;
printf("C: Sending Pointer %p to dart through port %ld\n", retptr, (int64_t) port);
char res = Dart_PostCObject_DL(port, &obj);
if (res == 0) {
printf("C: Failed to send message to Dart\n");
}
}
}