diff --git a/README.md b/README.md index 47d06be..088dd24 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,41 @@ ## 安装 -你可以使用去 [releases](https://git.taurusxin.com/taurusxin/ncmdump-go/releases/latest) 下载最新版预编译好的二进制文件,或者你也可以用包管理器来安装 +你可以前往 [releases](https://git.taurusxin.com/taurusxin/ncmdump-go/releases/latest) 下载最新版预编译好的二进制文件,或者使用包管理器安装。 + +### macOS + +项目同时支持 Apple Silicon(M1/M2/M3/M4 等)和 Intel Mac: + +- Apple Silicon 下载文件名包含 `darwin_arm64` 的压缩包; +- Intel Mac 下载文件名包含 `darwin_amd64` 的压缩包; +- 不确定芯片架构时,可运行 `uname -m`:输出 `arm64` 对应 Apple Silicon,输出 `x86_64` 对应 Intel。 + +下载并解压后,在终端执行: + +```shell +chmod +x ncmdump-go +sudo install -d /usr/local/bin +sudo install -m 0755 ncmdump-go /usr/local/bin/ncmdump-go +ncmdump-go --version +``` + +新发布的 macOS 压缩包已经包含可执行权限;这里保留 `chmod`,以兼容早期发布包。如果 macOS 提示无法验证开发者,可在确认文件来自本项目发布页后、执行安装命令前移除下载隔离属性: + +```shell +xattr -d com.apple.quarantine ncmdump-go +``` + +也可以安装 Go 1.23 或更高版本后从源码构建: + +```shell +go build -o ncmdump-go . +``` ```shell # Windows Scoop scoop bucket add taurusxin https://git.taurusxin.com/taurusxin/scoop-bucket.git # 添加 scoop 源 scoop install ncmdump-go # 安装 ncmdump-go - -# macOS & Linux 之后会支持 ``` ## 使用方法 @@ -69,10 +96,10 @@ ncmdump-go -d source_dir -o output_dir -r ## 开发 -使用 go module 下载 ncmdump-go 包 +使用 Go module 下载 ncmdump-go 包 ```shell -go get -u git.taurusxin.com/taurusxin/ncmdump-go +go get git.taurusxin.com/taurusxin/ncmdump-go@latest ``` 导入并使用 @@ -111,4 +138,3 @@ func main() { } } ``` - diff --git a/build.sh b/build.sh index 0db2a81..c231ae5 100755 --- a/build.sh +++ b/build.sh @@ -1,37 +1,63 @@ #!/usr/bin/env bash -VERSION=1.7.5 +set -euo pipefail -# Clean up the build directory -rm -rf build -mkdir build +VERSION="${VERSION:-1.7.5}" +VERSION="${VERSION#v}" +BUILD_DIR="${BUILD_DIR:-build}" +PACKAGE="git.taurusxin.com/taurusxin/ncmdump-go" +LDFLAGS="-w -s -X main.version=${VERSION}" -# Linux amd64 -echo "Building for Linux amd64..." -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o build/ncmdump-go git.taurusxin.com/taurusxin/ncmdump-go -tar zcf build/ncmdump-go_linux_amd64_$VERSION.tar.gz -C build ncmdump-go -rm build/ncmdump-go +mkdir -p "${BUILD_DIR}" +rm -f \ + "${BUILD_DIR}/ncmdump-go" \ + "${BUILD_DIR}/ncmdump-go.exe" \ + "${BUILD_DIR}/ncmdump-go_linux_amd64_${VERSION}.tar.gz" \ + "${BUILD_DIR}/ncmdump-go_linux_arm64_${VERSION}.tar.gz" \ + "${BUILD_DIR}/ncmdump-go_darwin_amd64_${VERSION}.tar.gz" \ + "${BUILD_DIR}/ncmdump-go_darwin_arm64_${VERSION}.tar.gz" \ + "${BUILD_DIR}/ncmdump-go_windows_amd64_${VERSION}.zip" -# Linux arm64 -echo "Building for Linux arm64..." -CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o build/ncmdump-go git.taurusxin.com/taurusxin/ncmdump-go -tar zcf build/ncmdump-go_linux_arm64_$VERSION.tar.gz -C build ncmdump-go -rm build/ncmdump-go +create_tar_archive() { + local os="$1" + local arch="$2" + local binary="${BUILD_DIR}/ncmdump-go" + local archive="${BUILD_DIR}/ncmdump-go_${os}_${arch}_${VERSION}.tar.gz" + local tar_version -# macOS amd64 -echo "Building for macOS amd64..." -CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-w -s" -o build/ncmdump-go git.taurusxin.com/taurusxin/ncmdump-go -tar zcf build/ncmdump-go_darwin_amd64_$VERSION.tar.gz -C build ncmdump-go -rm build/ncmdump-go + echo "Building for ${os} ${arch}..." + CGO_ENABLED=0 GOOS="${os}" GOARCH="${arch}" \ + go build -trimpath -ldflags="${LDFLAGS}" -o "${binary}" "${PACKAGE}" -# macOS arm64 -echo "Building for macOS arm64..." -CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-w -s" -o build/ncmdump-go git.taurusxin.com/taurusxin/ncmdump-go -tar zcf build/ncmdump-go_darwin_arm64_$VERSION.tar.gz -C build ncmdump-go -rm build/ncmdump-go + # Builds are sometimes produced from Git Bash on Windows, where chmod does + # not change NTFS permissions. Force the executable bit into GNU tar's + # header so macOS/Linux users can run the extracted binary immediately. + chmod 0755 "${binary}" + tar_version="$(tar --version 2>/dev/null || true)" + if [[ "${tar_version}" == *"GNU tar"* ]]; then + tar --mode=0755 -zcf "${archive}" -C "${BUILD_DIR}" ncmdump-go + else + tar -zcf "${archive}" -C "${BUILD_DIR}" ncmdump-go + fi + + if ! tar -tvzf "${archive}" | awk 'NR == 1 { exit(substr($1, 1, 10) == "-rwxr-xr-x" ? 0 : 1) }'; then + echo "Archive validation failed: ncmdump-go is not executable in ${archive}" >&2 + exit 1 + fi + + rm "${binary}" +} + +create_tar_archive linux amd64 +create_tar_archive linux arm64 +create_tar_archive darwin amd64 +create_tar_archive darwin arm64 # Windows amd64 echo "Building for Windows amd64..." -CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-w -s" -o build/ncmdump-go.exe git.taurusxin.com/taurusxin/ncmdump-go -zip -q -j build/ncmdump-go_windows_amd64_$VERSION.zip ./build/ncmdump-go.exe -rm build/ncmdump-go.exe \ No newline at end of file +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \ + go build -trimpath -ldflags="${LDFLAGS}" -o "${BUILD_DIR}/ncmdump-go.exe" "${PACKAGE}" +zip -q -j "${BUILD_DIR}/ncmdump-go_windows_amd64_${VERSION}.zip" "${BUILD_DIR}/ncmdump-go.exe" +rm "${BUILD_DIR}/ncmdump-go.exe" + +echo "Release archives written to ${BUILD_DIR}/" diff --git a/main.go b/main.go index eb0fa58..e8874fc 100644 --- a/main.go +++ b/main.go @@ -2,17 +2,27 @@ package main import ( "fmt" - "git.taurusxin.com/taurusxin/ncmdump-go/ncmcrypt" - "git.taurusxin.com/taurusxin/ncmdump-go/utils" "os" "path/filepath" + "strings" + + "git.taurusxin.com/taurusxin/ncmdump-go/ncmcrypt" + "git.taurusxin.com/taurusxin/ncmdump-go/utils" flag "github.com/spf13/pflag" ) +// version is replaced by build.sh for release builds. +var version = "1.7.5" + +func isNCMFile(filePath string) bool { + baseName := filepath.Base(filePath) + return !strings.HasPrefix(baseName, "._") && strings.EqualFold(filepath.Ext(baseName), ".ncm") +} + func processFile(filePath string, outputDir string) error { // skip if the extension is not .ncm - if filePath[len(filePath)-4:] != ".ncm" { + if !isNCMFile(filePath) { return nil } @@ -59,7 +69,7 @@ func main() { } if *showVersion { - fmt.Println("ncmdump version 1.7.5") + fmt.Printf("ncmdump version %s\n", version) os.Exit(0) } @@ -132,7 +142,7 @@ func main() { // process files from args for _, filePath := range flag.Args() { // skip if the extension is not .ncm - if filePath[len(filePath)-4:] != ".ncm" { + if !isNCMFile(filePath) { continue } if outputDirSpecified { diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..91df7e5 --- /dev/null +++ b/main_test.go @@ -0,0 +1,27 @@ +package main + +import "testing" + +func TestIsNCMFile(t *testing.T) { + tests := []struct { + name string + path string + want bool + }{ + {name: "lowercase", path: "/Users/test/Music/song.ncm", want: true}, + {name: "uppercase", path: "/Users/test/Music/song.NCM", want: true}, + {name: "unicode path", path: "/Users/test/音乐/歌曲.ncm", want: true}, + {name: "macOS AppleDouble file", path: "/Users/test/Music/._song.ncm", want: false}, + {name: "short name", path: "a", want: false}, + {name: "extension only", path: ".ncm", want: true}, + {name: "different extension", path: "song.flac", want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isNCMFile(tt.path); got != tt.want { + t.Fatalf("isNCMFile(%q) = %v, want %v", tt.path, got, tt.want) + } + }) + } +}