# https://taskfile.dev/ version: '3' includes: packaging: taskfile: ./Taskfile.package.yml flatten: true publishing: taskfile: ./Taskfile.publish.yml flatten: true vars: PROJECT_NAME: "ShadowsocksX-NG2" BUILD_DIR: "{{ .TASKFILE_DIR }}/.build" DERIVED_DATA_DIR: '{{default (printf "%s/derivedData" .BUILD_DIR) .DERIVED_DATA_DIR}}' RELEASE_VERSION_PATTERN: '^v?[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$' TEST_TIMEOUT_ARGS: '-test-timeouts-enabled YES -default-test-execution-time-allowance 120 -maximum-test-execution-time-allowance 300' tasks: validate: desc: 'Run validation gates in order and retain logs/results; stop on first failure' dir: '{{.TASKFILE_DIR}}' silent: true vars: GATES: '{{default "lint test build" .CLI_ARGS | trim}}' RUN_DIR: sh: | mkdir -p {{.BUILD_DIR | shellQuote}}/validation mktemp -d {{.BUILD_DIR | shellQuote}}/validation/$(date -u +%Y%m%dT%H%M%SZ)-XXXXXX preconditions: - sh: '{{if regexMatch "^(format:check|lint|test|test:smoke|build)( (format:check|lint|test|test:smoke|build))*$" .GATES}}true{{else}}false{{end}}' msg: 'Choose validation gates: format:check, lint, test, test:smoke, build (space separated).' - sh: '{{if eq (len (splitList " " .GATES)) (len (uniq (splitList " " .GATES)))}}true{{else}}false{{end}}' msg: 'Choose each validation gate at most once.' cmds: - defer: task: validate:summary vars: RUN_DIR: '{{.RUN_DIR}}' GATES: '{{.GATES}}' CODE: '{{default "0" .EXIT_CODE}}' - "printf '%s\n' {{printf \"Validation results: %s\" .RUN_DIR | shellQuote}}" - for: {var: GATES, split: ' '} task: validate:gate vars: RUN_DIR: '{{.RUN_DIR}}' GATE: '{{.ITEM}}' validate:gate: internal: true dir: '{{.TASKFILE_DIR}}' silent: true vars: GATE_DIR: '{{.RUN_DIR}}/{{.GATE | replace ":" "-"}}' cmds: - mkdir -p {{.GATE_DIR | shellQuote}} - defer: task: validate:result vars: GATE: '{{.GATE}}' GATE_DIR: '{{.GATE_DIR}}' CODE: '{{default "0" .EXIT_CODE}}' - "printf '%s\n' {{printf \"[%s] running; log: %s/raw.log\" .GATE .GATE_DIR | shellQuote}}" - >- task --exit-code --taskfile {{.TASKFILE_DIR | shellQuote}}/Taskfile.dist.yml {{.GATE | shellQuote}} RESULT_BUNDLE_PATH={{.GATE_DIR | shellQuote}}/result.xcresult > {{.GATE_DIR | shellQuote}}/raw.log 2>&1 validate:result: internal: true silent: true cmds: - | status=failed if [ {{.CODE | shellQuote}} = 0 ]; then status=passed; fi echo "[{{.GATE}}] $status; exit={{.CODE}}" > {{.GATE_DIR | shellQuote}}/status.txt - cmd: | {{if and (ne .GATE "lint") (ne .GATE "format:check")}} if [ ! -d {{.GATE_DIR | shellQuote}}/result.xcresult ]; then echo '[{{.GATE}}] evidence incomplete: result bundle missing; test counts unknown' >> {{.GATE_DIR | shellQuote}}/status.txt {{if ne .GATE "build"}} elif xcrun xcresulttool get test-results summary --path {{.GATE_DIR | shellQuote}}/result.xcresult > {{.GATE_DIR | shellQuote}}/test-summary.json 2> {{.GATE_DIR | shellQuote}}/test-summary.stderr.log; then if passed=$(plutil -extract passedTests raw -expect integer {{.GATE_DIR | shellQuote}}/test-summary.json) && failed=$(plutil -extract failedTests raw -expect integer {{.GATE_DIR | shellQuote}}/test-summary.json) && skipped=$(plutil -extract skippedTests raw -expect integer {{.GATE_DIR | shellQuote}}/test-summary.json) && expected=$(plutil -extract expectedFailures raw -expect integer {{.GATE_DIR | shellQuote}}/test-summary.json) && total=$(plutil -extract totalTestCount raw -expect integer {{.GATE_DIR | shellQuote}}/test-summary.json); then echo "[{{.GATE}}] tests: passed=$passed failed=$failed skipped=$skipped expectedFailures=$expected total=$total" >> {{.GATE_DIR | shellQuote}}/status.txt else printf '%s\n' "$passed" "$failed" "$skipped" "$expected" "$total" >> {{.GATE_DIR | shellQuote}}/count-reader.log echo '[{{.GATE}}] evidence incomplete: invalid test summary; test counts unknown (see count-reader.log)' >> {{.GATE_DIR | shellQuote}}/status.txt fi 2> {{.GATE_DIR | shellQuote}}/count-reader.log else echo '[{{.GATE}}] evidence incomplete: xcresulttool failed; test counts unknown (see test-summary.stderr.log)' >> {{.GATE_DIR | shellQuote}}/status.txt {{end}} fi {{end}} - cat {{.GATE_DIR | shellQuote}}/status.txt validate:summary: internal: true silent: true cmds: - | echo 'Validation command exit={{.CODE}}' > {{.RUN_DIR | shellQuote}}/summary.txt echo 'Selected gates: {{.GATES}}' >> {{.RUN_DIR | shellQuote}}/summary.txt - for: {var: GATES, split: ' '} cmd: | if [ -f {{.RUN_DIR | shellQuote}}/{{.ITEM | replace ":" "-"}}/status.txt ]; then cat {{.RUN_DIR | shellQuote}}/{{.ITEM | replace ":" "-"}}/status.txt >> {{.RUN_DIR | shellQuote}}/summary.txt else echo '[{{.ITEM}}] not_run' >> {{.RUN_DIR | shellQuote}}/summary.txt fi - cat {{.RUN_DIR | shellQuote}}/summary.txt - "printf '%s\n' {{printf \"Summary: %s/summary.txt\" .RUN_DIR | shellQuote}}" gen:prj: desc: 'Generate xcode project by xcodegen' cmds: - xcodegen generate build: desc: Build the ShadowsocksX-NG2 macOS application with Debug Config deps: - gen:prj cmds: - >- xcodebuild -project ShadowsocksX-NG2.xcodeproj -scheme ShadowsocksX-NG2 -configuration Debug -destination 'platform=macOS,arch=arm64' -derivedDataPath {{.DERIVED_DATA_DIR | shellQuote}} {{if .RESULT_BUNDLE_PATH}}-resultBundlePath {{.RESULT_BUNDLE_PATH | shellQuote}}{{end}} SWIFT_SUPPRESS_WARNINGS=NO SWIFT_TREAT_WARNINGS_AS_ERRORS=YES GCC_TREAT_WARNINGS_AS_ERRORS=YES build build:release: desc: Build the ShadowsocksX-NG2 macOS application with Release Config requires: vars: [MARKETING_VERSION, CURRENT_PROJECT_VERSION] vars: BUNDLE_MARKETING_VERSION: '{{regexFind "[0-9]+\\.[0-9]+\\.[0-9]+" (default "" .MARKETING_VERSION)}}' preconditions: - sh: '{{if regexMatch .RELEASE_VERSION_PATTERN .MARKETING_VERSION}}true{{else}}false{{end}}' msg: 'MARKETING_VERSION must be a release version (for example, 2.1.0 or v2.0.0-alpha-1).' - sh: '{{if regexMatch "^[0-9]+(\\.[0-9]+){0,2}$" .CURRENT_PROJECT_VERSION}}true{{else}}false{{end}}' msg: 'CURRENT_PROJECT_VERSION must have one to three numeric components.' cmds: - task: gen:prj - >- xcodebuild -project ShadowsocksX-NG2.xcodeproj -scheme ShadowsocksX-NG2 -configuration Release -destination 'platform=macOS,arch=arm64' -derivedDataPath {{.DERIVED_DATA_DIR | shellQuote}} {{printf "MARKETING_VERSION=%s" .BUNDLE_MARKETING_VERSION | shellQuote}} {{printf "CURRENT_PROJECT_VERSION=%s" .CURRENT_PROJECT_VERSION | shellQuote}} SWIFT_SUPPRESS_WARNINGS=NO SWIFT_TREAT_WARNINGS_AS_ERRORS=YES GCC_TREAT_WARNINGS_AS_ERRORS=YES build format: desc: 'Format the code by swift-format' cmds: - swift format format --in-place --recursive --configuration .swift-format App Tests Domain Agent format:check: desc: 'Check Swift formatting without modifying files' cmds: - swift format lint --strict --recursive --configuration .swift-format App Tests Domain Agent lint: desc: 'Lint the swift code by swiftlint' cmds: - swiftlint lint --strict --config .swiftlint.yml --no-cache lint:fix: desc: 'Automatically fix SwiftLint violations' cmds: - swiftlint lint --fix --strict --config .swiftlint.yml --no-cache test: desc: 'Run conversion and unit tests' deps: - gen:prj cmds: - task: test:binaries - task: test:rules - task: test:metadata - >- xcodebuild -project ShadowsocksX-NG2.xcodeproj -scheme ShadowsocksX-NG2 -destination 'platform=macOS,arch=arm64' -derivedDataPath {{.DERIVED_DATA_DIR | shellQuote}} {{if .RESULT_BUNDLE_PATH}}-resultBundlePath {{.RESULT_BUNDLE_PATH | shellQuote}}{{end}} {{.TEST_TIMEOUT_ARGS}} SWIFT_SUPPRESS_WARNINGS=NO SWIFT_TREAT_WARNINGS_AS_ERRORS=YES GCC_TREAT_WARNINGS_AS_ERRORS=YES test test:binaries: desc: 'Verify pinned external binary downloads with local assets' cmds: - PYTHONDONTWRITEBYTECODE=1 python3 Scripts/test-external-binary-fetch.py test:metadata: desc: 'Verify offline dependency metadata generation and rejection paths' cmds: - PYTHONDONTWRITEBYTECODE=1 python3 Scripts/test-dependency-metadata.py test:rules: desc: 'Run offline rule conversion and generated fixture checks' cmds: - PYTHONDONTWRITEBYTECODE=1 python3 Scripts/test-rule-snapshot-converters.py - PYTHONDONTWRITEBYTECODE=1 python3 Scripts/test-rule-conversion-behavior.py test:smoke: desc: 'Run real-process smoke tests (real sslocal; release gate)' deps: - gen:prj cmds: - >- xcodebuild -project ShadowsocksX-NG2.xcodeproj -scheme ShadowsocksX-NG2Smoke -destination 'platform=macOS,arch=arm64' -derivedDataPath {{.DERIVED_DATA_DIR | shellQuote}} {{if .RESULT_BUNDLE_PATH}}-resultBundlePath {{.RESULT_BUNDLE_PATH | shellQuote}}{{end}} {{.TEST_TIMEOUT_ARGS}} SWIFT_SUPPRESS_WARNINGS=NO SWIFT_TREAT_WARNINGS_AS_ERRORS=YES GCC_TREAT_WARNINGS_AS_ERRORS=YES test clean: cmds: - rm -rf {{ .BUILD_DIR }}