├── .github
├── renovate.json
└── workflows
│ └── ci.yml
├── .gitignore
├── .goreleaser.yml
├── LICENSE
├── README.md
├── go.mod
├── go.sum
├── internal
├── localcheck
│ ├── arch.go
│ ├── arch_test.go
│ ├── file.go
│ ├── file_test.go
│ ├── local.go
│ └── local_test.go
├── macapp
│ ├── macapp.go
│ └── macapp_test.go
├── output
│ ├── output.go
│ └── output_test.go
└── remotecheck
│ ├── err.go
│ ├── remote.go
│ ├── remote_test.go
│ ├── support.go
│ └── support_test.go
├── main.go
└── test
└── data
├── bash.sh
├── env_bash.sh
├── env_invalid.sh
├── example_fat.app
└── Contents
│ ├── Info.plist
│ └── MacOS
│ └── example
├── example_macho.app
└── Contents
│ ├── Info.plist
│ └── MacOS
│ └── example
├── invalid_interpreter.app
└── Contents
│ ├── Info.plist
│ └── MacOS
│ └── run.sh
├── invalid_plist.app
└── Contents
│ └── Info.plist
├── invalid_shebang.sh
├── sh_app.app
└── Contents
│ ├── Info.plist
│ └── MacOS
│ └── run.sh
└── unknown_type.app
└── Contents
└── Info.plist
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base",
4 | ":disableDependencyDashboard"
5 | ],
6 | "labels": ["dependencies"],
7 | "packageRules": [
8 | {
9 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"],
10 | "automerge": true
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | strategy:
8 | matrix:
9 | go-version: ['1.20', '1.19' ]
10 | os: [macos-latest, ubuntu-latest]
11 | runs-on: ${{ matrix.os }}
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v3
15 |
16 | - name: Set up Go
17 | uses: actions/setup-go@v4
18 | with:
19 | go-version: ${{ matrix.go-version }}
20 |
21 | - name: Checkout code
22 | uses: actions/checkout@v3
23 |
24 | - name: Build
25 | run: go build -v ./...
26 |
27 | - name: Test with coverage
28 | run: go test -v ./... -cover -coverprofile=coverage.txt -covermode=atomic
29 |
30 | - name: Upload coverage to Codecov
31 | uses: codecov/codecov-action@v3
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ### Go ###
3 | # Binaries for programs and plugins
4 | *.exe
5 | *.exe~
6 | *.dll
7 | *.so
8 | *.dylib
9 |
10 | # Test binary, built with `go test -c`
11 | *.test
12 |
13 | # Output of the go coverage tool, specifically when used with LiteIDE
14 | *.out
15 |
16 | # Dependency directories (remove the comment below to include it)
17 | # vendor/
18 |
19 | ### Go Patch ###
20 | /vendor/
21 | /Godeps/
22 |
23 | ### vscode ###
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | *.code-workspace
30 |
31 | ### Dist Releases ###
32 | dist/
33 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | # This is an example .goreleaser.yml file with some sane defaults.
2 | # Make sure to check the documentation at http://goreleaser.com
3 | before:
4 | hooks:
5 | - go mod tidy
6 | # - go generate ./...
7 | builds:
8 | - env:
9 | - CGO_ENABLED=0
10 | goos:
11 | - darwin
12 | archives:
13 | - replacements:
14 | darwin: Darwin
15 | linux: Linux
16 | windows: Windows
17 | 386: i386
18 | amd64: x86_64
19 | checksum:
20 | name_template: 'checksums.txt'
21 | snapshot:
22 | name_template: "{{ .Tag }}-next"
23 | changelog:
24 | sort: asc
25 | filters:
26 | exclude:
27 | - '^docs:'
28 | - '^test:'
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Chongyi Zheng
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Apple Silicon Check
2 |
3 | [](https://github.com/harryzcy/ascheck/actions)
4 | [](https://codecov.io/gh/harryzcy/ascheck)
5 | [](https://goreportcard.com/report/github.com/harryzcy/ascheck)
6 | [](http://makeapullrequest.com)
7 |
8 | A CLI tool that bulk-checks your apps for the Apple Silicon support.
9 |
10 | ---
11 |
12 | ## Table of Contents
13 |
14 | - [Installation](#installation)
15 | - [Homebrew tap](#homebrew-tap)
16 | - [go install](#go-install)
17 | - [Compile from source](#compile-from-source)
18 | - [Example Usage](#example-usage)
19 | - [Show help](#show-help)
20 | - [Run](#run)
21 | - [Output](#output)
22 |
23 | ---
24 |
25 | ## Installation
26 |
27 | ### Homebrew tap
28 |
29 | ```shell
30 | brew tap harryzcy/ascheck
31 | brew install ascheck
32 | ```
33 |
34 | ### go install
35 |
36 | ```shell
37 | go install github.com/harryzcy/ascheck
38 | ```
39 |
40 | ### Compile from source
41 |
42 | #### clone
43 |
44 | ```shell
45 | git clone https://github.com/harryzheng/ascheck
46 | cd ascheck
47 | ```
48 |
49 | #### get the dependencies
50 |
51 | ```shell
52 | go mod tidy
53 | ```
54 |
55 | #### build
56 |
57 | ```shell
58 | go build -o ascheck .
59 | ```
60 |
61 | ## Example Usage
62 |
63 | ### Show help
64 |
65 | ```shell
66 | ascheck -h
67 | ```
68 |
69 | ### Run
70 |
71 | ```shell
72 | ascheck
73 | ```
74 |
75 | ### Output
76 |
77 | The output will show:
78 |
79 | ```shell
80 | NAME CURRENT ARCHITECTURES ARM SUPPORT
81 | ------------------------------------------------
82 | App Store Intel 64 Supported
83 | Automator Intel 64 Supported
84 | ...
85 | ```
86 |
87 | - NAME: name of the app
88 | - CURRENT ARCHITECTURES: the architecture of the currently installed version
89 | - ARM SUPPORT: the arm support information on [Does it Arm](https://github.com/ThatGuySam/doesitarm)
90 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/harryzcy/ascheck
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/olekukonko/tablewriter v0.0.5
7 | github.com/stretchr/testify v1.8.2
8 | github.com/urfave/cli/v2 v2.25.2
9 | howett.net/plist v1.0.0
10 | )
11 |
12 | require (
13 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
14 | github.com/davecgh/go-spew v1.1.1 // indirect
15 | github.com/mattn/go-runewidth v0.0.14 // indirect
16 | github.com/pmezard/go-difflib v1.0.0 // indirect
17 | github.com/rivo/uniseg v0.4.4 // indirect
18 | github.com/russross/blackfriday/v2 v2.1.0 // indirect
19 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
20 | gopkg.in/yaml.v3 v3.0.1 // indirect
21 | )
22 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
2 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
7 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
8 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
9 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
10 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
11 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
14 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
15 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
16 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
17 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
18 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
19 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
20 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
21 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
22 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
23 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
24 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
25 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
26 | github.com/urfave/cli/v2 v2.25.0 h1:ykdZKuQey2zq0yin/l7JOm9Mh+pg72ngYMeB0ABn6q8=
27 | github.com/urfave/cli/v2 v2.25.0/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
28 | github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw=
29 | github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
30 | github.com/urfave/cli/v2 v2.25.2 h1:rgeK7wmjwH+d3DqXDDSV20GZAvNzmzu/VEsg1om3Qwg=
31 | github.com/urfave/cli/v2 v2.25.2/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
32 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
33 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
34 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
35 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
36 | gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
37 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
38 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
39 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
40 | howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
41 | howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
42 |
--------------------------------------------------------------------------------
/internal/localcheck/arch.go:
--------------------------------------------------------------------------------
1 | package localcheck
2 |
3 | import (
4 | "debug/macho"
5 | "strings"
6 | )
7 |
8 | // Architectures represents all supported architecture of an app.
9 | type Architectures struct {
10 | Intel uint
11 | Arm uint
12 | PowerPC uint
13 | }
14 |
15 | // Load loads the architectures from macho.Cpu.
16 | func (arch *Architectures) Load(cpu macho.Cpu) {
17 | switch cpu {
18 | case macho.Cpu386:
19 | arch.Intel |= 0b01
20 | case macho.CpuAmd64:
21 | arch.Intel |= 0b10
22 | case macho.CpuArm:
23 | arch.Arm |= 0b01
24 | case macho.CpuArm64:
25 | arch.Arm |= 0b10
26 | case macho.CpuPpc:
27 | arch.PowerPC |= 0b01
28 | case macho.CpuPpc64:
29 | arch.PowerPC |= 0b10
30 | }
31 | }
32 |
33 | // LoadFromFat loads the architectures from []macho.FatArch.
34 | func (arch *Architectures) LoadFromFat(src []macho.FatArch) {
35 | for _, fat := range src {
36 | arch.Load(fat.Cpu)
37 | }
38 | }
39 |
40 | // String returns the architecture in string format.
41 | func (arch *Architectures) String() string {
42 | var list []string
43 |
44 | if arch.PowerPC > 0 {
45 | list = append(list, "PowerPC "+getBitString(arch.PowerPC))
46 | }
47 | if arch.Intel > 0 {
48 | list = append(list, "Intel "+getBitString(arch.Intel))
49 | }
50 | if arch.Arm > 0 {
51 | list = append(list, "Arm "+getBitString(arch.Arm))
52 | }
53 |
54 | if len(list) > 0 {
55 | return strings.Join(list, ", ")
56 | }
57 |
58 | return "Unknown"
59 | }
60 |
61 | func getBitString(mask uint) string {
62 | switch mask {
63 | case 0b11:
64 | return "32/64"
65 | case 0b01:
66 | return "32"
67 | case 0b10:
68 | return "64"
69 | default:
70 | return ""
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/internal/localcheck/arch_test.go:
--------------------------------------------------------------------------------
1 | package localcheck
2 |
3 | import (
4 | "debug/macho"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestArchitecture_Load(t *testing.T) {
11 | tests := []struct {
12 | cpu macho.Cpu
13 | expected Architectures
14 | }{
15 | {macho.CpuPpc, Architectures{PowerPC: 0b01}},
16 | {macho.CpuPpc64, Architectures{PowerPC: 0b10}},
17 | {macho.Cpu386, Architectures{Intel: 0b01}},
18 | {macho.CpuAmd64, Architectures{Intel: 0b10}},
19 | {macho.CpuArm, Architectures{Arm: 0b01}},
20 | {macho.CpuArm64, Architectures{Arm: 0b10}},
21 | }
22 |
23 | for _, test := range tests {
24 | arch := Architectures{}
25 | assert.Empty(t, arch)
26 |
27 | arch.Load(test.cpu)
28 | assert.Equal(t, test.expected, arch)
29 | }
30 | }
31 |
32 | func TestArchitecture_LoadFat(t *testing.T) {
33 | tests := []struct {
34 | in []macho.FatArch
35 | expected Architectures
36 | }{
37 | {[]macho.FatArch{
38 | {FatArchHeader: macho.FatArchHeader{Cpu: macho.CpuAmd64}},
39 | },
40 | Architectures{Intel: 0b10},
41 | },
42 | {[]macho.FatArch{
43 | {FatArchHeader: macho.FatArchHeader{Cpu: macho.CpuAmd64}},
44 | {FatArchHeader: macho.FatArchHeader{Cpu: macho.CpuArm64}},
45 | },
46 | Architectures{Intel: 0b10, Arm: 0b10},
47 | },
48 | }
49 |
50 | for _, test := range tests {
51 | arch := Architectures{}
52 | assert.Empty(t, arch)
53 |
54 | arch.LoadFromFat(test.in)
55 | assert.Equal(t, test.expected, arch)
56 | }
57 | }
58 |
59 | func TestArchitecture_String(t *testing.T) {
60 | tests := []struct {
61 | arch Architectures
62 | expected string
63 | }{
64 | {Architectures{PowerPC: 0b01}, "PowerPC 32"},
65 | {Architectures{PowerPC: 0b10}, "PowerPC 64"},
66 | {Architectures{PowerPC: 0b11}, "PowerPC 32/64"},
67 | {Architectures{Intel: 0b01}, "Intel 32"},
68 | {Architectures{Intel: 0b10}, "Intel 64"},
69 | {Architectures{Intel: 0b11}, "Intel 32/64"},
70 | {Architectures{Arm: 0b01}, "Arm 32"},
71 | {Architectures{Arm: 0b10}, "Arm 64"},
72 | {Architectures{Arm: 0b11}, "Arm 32/64"},
73 |
74 | {Architectures{Intel: 0b10, Arm: 0b10}, "Intel 64, Arm 64"},
75 | {Architectures{Intel: 0b11, Arm: 0b10}, "Intel 32/64, Arm 64"},
76 | {Architectures{PowerPC: 0b11, Arm: 0b11}, "PowerPC 32/64, Arm 32/64"},
77 |
78 | {Architectures{}, "Unknown"},
79 | }
80 |
81 | for _, test := range tests {
82 | actual := test.arch.String()
83 | assert.Equal(t, test.expected, actual)
84 | }
85 | }
86 |
87 | func TestGetBitString_EdgeCase(t *testing.T) {
88 | assert.Empty(t, getBitString(0))
89 | }
90 |
--------------------------------------------------------------------------------
/internal/localcheck/file.go:
--------------------------------------------------------------------------------
1 | package localcheck
2 |
3 | import (
4 | "os"
5 | "unicode/utf8"
6 | )
7 |
8 | // IsText reports whether a significant prefix of s looks like correct UTF-8;
9 | // that is, if it is likely that s is human-readable text.
10 | func IsText(s []byte) bool {
11 | const max = 1024 // at least utf8.UTFMax
12 | if len(s) > max {
13 | s = s[0:max]
14 | }
15 | for i, c := range string(s) {
16 | if i+utf8.UTFMax > len(s) {
17 | // last char may be incomplete - ignore
18 | break
19 | }
20 | if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' {
21 | // decoding error or control character - not a text file
22 | return false
23 | }
24 | }
25 | return true
26 | }
27 |
28 | // IsTextFile reports if a significant chunk of the specified file looks like
29 | // correct UTF-8; that is, if it is likely that the file contains human-
30 | // readable text.
31 | func IsTextFile(filename string) bool {
32 | // read an initial chunk of the file
33 | // and check if it looks like text
34 | f, err := os.Open(filename)
35 | if err != nil {
36 | return false
37 | }
38 | defer f.Close()
39 |
40 | var buf [1024]byte
41 | n, err := f.Read(buf[0:])
42 | if err != nil {
43 | return false
44 | }
45 |
46 | return IsText(buf[0:n])
47 | }
48 |
--------------------------------------------------------------------------------
/internal/localcheck/file_test.go:
--------------------------------------------------------------------------------
1 | package localcheck
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestIsText(t *testing.T) {
11 | tests := []struct {
12 | in []byte
13 | expected bool
14 | }{
15 | {[]byte("#!/bin/bash\n"), true},
16 | {[]byte("some string"), true},
17 | {[]byte("#!/usr/bin/env bash\n"), true},
18 | {[]byte(strings.Repeat("some string ", 100)), true},
19 | {[]byte{0x00, 0x01, 0x02, 0x3}, false},
20 | }
21 |
22 | for _, test := range tests {
23 | actual := IsText(test.in)
24 | assert.Equal(t, test.expected, actual)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/internal/localcheck/local.go:
--------------------------------------------------------------------------------
1 | package localcheck
2 |
3 | import (
4 | "bufio"
5 | "debug/macho"
6 | "errors"
7 | "os"
8 | "os/exec"
9 | "path/filepath"
10 | "strings"
11 |
12 | "howett.net/plist"
13 | )
14 |
15 | type executableDecoded struct {
16 | CFBundleExecutable string
17 | }
18 |
19 | func getExecutableName(path string) (string, error) {
20 | plistFile := filepath.Join(path, "Contents", "Info.plist")
21 |
22 | f, err := os.Open(plistFile)
23 | if err != nil {
24 | return "", err
25 | }
26 | defer f.Close()
27 |
28 | decoder := plist.NewDecoder(f)
29 | var plistDecoded executableDecoded
30 | err = decoder.Decode(&plistDecoded)
31 | if err != nil {
32 | return "", err
33 | }
34 |
35 | return plistDecoded.CFBundleExecutable, err
36 | }
37 |
38 | // GetArchitectures returns all supported architecture given the app's path.
39 | func GetArchitectures(path string) (Architectures, error) {
40 | executableName, err := getExecutableName(path)
41 | if err != nil {
42 | return Architectures{}, err
43 | }
44 |
45 | // binary file path
46 | executable := filepath.Join(path, "Contents", "MacOS", executableName)
47 |
48 | return getExecutableArchitectures(executable)
49 | }
50 |
51 | func getExecutableArchitectures(path string) (Architectures, error) {
52 | var (
53 | arch = Architectures{}
54 | )
55 |
56 | // file is a Mach-O universal file
57 | fat, err := macho.OpenFat(path)
58 | if err == nil {
59 | arch.LoadFromFat(fat.Arches)
60 | return arch, nil
61 | }
62 |
63 | // file is a Mach-O file
64 | f, err := macho.Open(path)
65 | if err == nil {
66 | arch.Load(f.Cpu)
67 | return arch, nil
68 | }
69 |
70 | // file is a text file
71 | if IsTextFile(path) {
72 | interpreter, ok := getInterpreterPath(path)
73 | if !ok {
74 | return arch, errors.New("unable to get executable path")
75 | }
76 | return getExecutableArchitectures(interpreter)
77 | }
78 |
79 | return arch, errors.New("unknown file type")
80 | }
81 |
82 | func getInterpreterPath(filename string) (path string, ok bool) {
83 | f, err := os.Open(filename)
84 | if err != nil {
85 | return "", false
86 | }
87 | defer f.Close()
88 |
89 | // read the first line of the file; ensure that it starts with Shebang
90 | reader := bufio.NewReader(f)
91 | line, _ := reader.ReadString('\n')
92 | line = strings.TrimSuffix(line, "\n")
93 | if !strings.HasPrefix(line, "#!") {
94 | return "", false
95 | }
96 |
97 | line = line[2:] // skip Shebang
98 | if strings.HasPrefix(line, "/usr/bin/env") {
99 | line = line[13:] // skip logical path
100 |
101 | interpreter := strings.SplitN(line, " ", 2)[0]
102 | path, err := exec.LookPath(interpreter)
103 | if err != nil {
104 | return "", false
105 | }
106 |
107 | return path, true
108 | }
109 |
110 | path = strings.SplitN(line, " ", 2)[0]
111 |
112 | return path, true
113 | }
114 |
--------------------------------------------------------------------------------
/internal/localcheck/local_test.go:
--------------------------------------------------------------------------------
1 | package localcheck
2 |
3 | import (
4 | "errors"
5 | "os"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestApplication_GetExecutableName(t *testing.T) {
12 | exec, err := getExecutableName("./../../test/data/sh_app.app")
13 | assert.Nil(t, err)
14 | assert.Equal(t, "run.sh", exec)
15 | }
16 |
17 | func TestApplication_GetArchitectures(t *testing.T) {
18 | arch, err := GetArchitectures("./../../test/data/example_macho.app")
19 | assert.Nil(t, err)
20 | assert.EqualValues(t, 0b01, arch.Intel)
21 | assert.EqualValues(t, 0, arch.PowerPC)
22 | assert.EqualValues(t, 0, arch.Arm)
23 |
24 | arch, err = GetArchitectures("./../../test/data/example_fat.app")
25 | assert.Nil(t, err)
26 | assert.EqualValues(t, 0b10, arch.Intel)
27 | assert.EqualValues(t, 0, arch.PowerPC)
28 | assert.EqualValues(t, 0, arch.Arm)
29 |
30 | arch, err = GetArchitectures("./../../test/data/sh_app.app")
31 | if err == nil { // should pass on macOS
32 | assert.NotEmpty(t, arch)
33 | } else { // would failed on linux
34 | assert.Equal(t, errors.New("unknown file type"), err)
35 | assert.Empty(t, arch)
36 | }
37 | }
38 |
39 | func TestApplication_GetArchitectures_Error(t *testing.T) {
40 | // Invalid path
41 | arch, err := GetArchitectures("./../../test/data/invalid.app")
42 | assert.NotNil(t, err)
43 | assert.True(t, os.IsNotExist(err))
44 | assert.Empty(t, arch)
45 |
46 | // Invalid plist
47 | arch, err = GetArchitectures("./../../test/data/invalid_plist.app")
48 | assert.NotNil(t, err)
49 | assert.False(t, os.IsNotExist(err))
50 | assert.Empty(t, arch)
51 |
52 | // Invalid interpreter
53 | arch, err = GetArchitectures("./../../test/data/invalid_interpreter.app")
54 | assert.NotNil(t, err)
55 | assert.Equal(t, errors.New("unable to get executable path"), err)
56 | assert.Empty(t, arch)
57 |
58 | // Unknown file type
59 | arch, err = GetArchitectures("./../../test/data/unknown_type.app")
60 | assert.NotNil(t, err)
61 | assert.Equal(t, errors.New("unknown file type"), err)
62 | assert.Empty(t, arch)
63 | }
64 |
65 | func TestGetInterpreterPath(t *testing.T) {
66 | tests := []struct {
67 | file string
68 | expectedPath []string
69 | expectedOK bool
70 | }{
71 | {"./../../test/data/bash.sh", []string{"/bin/bash"}, true},
72 | {"./../../test/data/env_bash.sh", []string{"/bin/bash", "/usr/bin/bash"}, true},
73 | {"./../../test/data/invalid.sh", []string{""}, false},
74 | {"./../../test/data/env_invalid.sh", []string{""}, false},
75 | {"./../../test/data/invalid_shebang.sh", []string{""}, false},
76 | }
77 |
78 | for _, test := range tests {
79 | path, ok := getInterpreterPath(test.file)
80 | assert.Contains(t, test.expectedPath, path)
81 | assert.Equal(t, test.expectedOK, ok)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/internal/macapp/macapp.go:
--------------------------------------------------------------------------------
1 | package macapp
2 |
3 | import (
4 | "os"
5 | "os/user"
6 | "path/filepath"
7 | "strings"
8 |
9 | "github.com/harryzcy/ascheck/internal/localcheck"
10 | "github.com/harryzcy/ascheck/internal/remotecheck"
11 | )
12 |
13 | var (
14 | applicationPath []string
15 | )
16 |
17 | func init() {
18 | usr, _ := user.Current()
19 | userApplication := filepath.Join(usr.HomeDir, "Applications")
20 |
21 | applicationPath = []string{
22 | "/System/Applications",
23 | "/Applications",
24 | userApplication,
25 | }
26 | }
27 |
28 | // Application represents an installed app.
29 | type Application struct {
30 | // Name shows the app name
31 | Name string
32 |
33 | // Path shows the physical location
34 | Path string
35 | // Architectures represents the architectures of the currently installed version
36 | Architectures localcheck.Architectures
37 |
38 | // Website shows the app's website, empty if unknown
39 | Website string
40 | // ArmSupport shows the Apple Silicon support based on Does It Arm reports
41 | ArmSupport remotecheck.Support
42 | }
43 |
44 | // GetAllApplications returns all applications.
45 | func GetAllApplications(dirs []string) ([]Application, error) {
46 | var (
47 | applications []Application
48 | )
49 |
50 | if dirs == nil {
51 | dirs = applicationPath
52 | }
53 |
54 | for _, dir := range dirs {
55 | entries, err := os.ReadDir(dir)
56 | if err != nil {
57 | if os.IsNotExist(err) {
58 | continue
59 | }
60 | return nil, err
61 | }
62 |
63 | for _, entry := range entries {
64 | if strings.HasSuffix(entry.Name(), ".app") {
65 | app := checkApplication(dir, entry)
66 | applications = append(applications, app)
67 | }
68 | }
69 | }
70 |
71 | return applications, nil
72 | }
73 |
74 | func checkApplication(dir string, entry os.DirEntry) Application {
75 | app := Application{
76 | Name: strings.TrimSuffix(entry.Name(), ".app"),
77 | Path: filepath.Join(dir, entry.Name()),
78 | }
79 |
80 | app.Architectures, _ = localcheck.GetArchitectures(app.Path)
81 |
82 | // mark system apps as natively supported
83 | if strings.HasPrefix(dir, "/System/") {
84 | app.ArmSupport = remotecheck.SupportNative
85 | return app
86 | }
87 |
88 | info, err := remotecheck.GetInfo(app.Name)
89 | if err == nil {
90 | app.Website = info.Website
91 | app.ArmSupport = info.ArmSupport
92 | }
93 |
94 | return app
95 | }
96 |
--------------------------------------------------------------------------------
/internal/macapp/macapp_test.go:
--------------------------------------------------------------------------------
1 | package macapp
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestGetAllApplications(t *testing.T) {
10 | apps, err := GetAllApplications(nil)
11 | assert.Nil(t, err)
12 | assert.IsType(t, []Application{}, apps)
13 | }
14 |
--------------------------------------------------------------------------------
/internal/output/output.go:
--------------------------------------------------------------------------------
1 | package output
2 |
3 | import (
4 | "encoding/json"
5 | "io"
6 | "os"
7 |
8 | "github.com/harryzcy/ascheck/internal/macapp"
9 | "github.com/olekukonko/tablewriter"
10 | )
11 |
12 | var out io.Writer = os.Stdout
13 |
14 | // Table prints application information in table format.
15 | func Table(apps []macapp.Application) {
16 | table := tablewriter.NewWriter(out)
17 | table.SetHeader([]string{"Name", "Current Architectures", "Arm Support"})
18 | table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
19 | table.SetCenterSeparator("")
20 | table.SetColumnSeparator("")
21 | table.SetBorder(false)
22 |
23 | for _, app := range apps {
24 | table.Append([]string{app.Name, app.Architectures.String(), app.ArmSupport.String()})
25 | }
26 |
27 | table.Render()
28 | }
29 |
30 | // JSON prints application information in json format.
31 | func JSON(apps []macapp.Application) {
32 | items := make([]map[string]string, len(apps))
33 |
34 | for idx, app := range apps {
35 | row := map[string]string{
36 | "name": app.Name,
37 | "currentArchitectures": app.Architectures.String(),
38 | "armSupport": app.ArmSupport.String(),
39 | }
40 | items[idx] = row
41 | }
42 |
43 | output, _ := json.Marshal(map[string]interface{}{
44 | "items": items,
45 | })
46 | output = append(output, byte('\n'))
47 | out.Write(output)
48 | }
49 |
--------------------------------------------------------------------------------
/internal/output/output_test.go:
--------------------------------------------------------------------------------
1 | package output
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/harryzcy/ascheck/internal/localcheck"
8 | "github.com/harryzcy/ascheck/internal/macapp"
9 | "github.com/harryzcy/ascheck/internal/remotecheck"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | var str = new(strings.Builder)
14 |
15 | func init() {
16 | out = str
17 | }
18 |
19 | func TestTable(t *testing.T) {
20 | apps := []macapp.Application{
21 | {Name: "a", Architectures: localcheck.Architectures{Intel: 0b10}, ArmSupport: remotecheck.SupportNative},
22 | {Name: "b", Architectures: localcheck.Architectures{Intel: 0b10}, ArmSupport: remotecheck.SupportNative},
23 | }
24 |
25 | str.Reset()
26 |
27 | Table(apps)
28 |
29 | assert.Equal(t, ""+
30 | " NAME CURRENT ARCHITECTURES ARM SUPPORT \n"+
31 | "--------------------------------------------\n"+
32 | " a Intel 64 Supported \n"+
33 | " b Intel 64 Supported \n",
34 | str.String())
35 |
36 | }
37 |
38 | func TestJSON(t *testing.T) {
39 | apps := []macapp.Application{
40 | {Name: "a", Architectures: localcheck.Architectures{Intel: 0b10}, ArmSupport: remotecheck.SupportNative},
41 | }
42 |
43 | str.Reset()
44 |
45 | JSON(apps)
46 |
47 | assert.Equal(t, `{"items":[{"armSupport":"Supported","currentArchitectures":"Intel 64","name":"a"}]}`+"\n",
48 | str.String(),
49 | )
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/internal/remotecheck/err.go:
--------------------------------------------------------------------------------
1 | package remotecheck
2 |
3 | import (
4 | "errors"
5 | )
6 |
7 | var (
8 | // ErrNotFound is returned when an app is not found.
9 | ErrNotFound = errors.New("app not found")
10 | )
11 |
--------------------------------------------------------------------------------
/internal/remotecheck/remote.go:
--------------------------------------------------------------------------------
1 | package remotecheck
2 |
3 | import (
4 | "io/ioutil"
5 | "net/http"
6 | "regexp"
7 | )
8 |
9 | const (
10 | sourceURL = "https://cdn.jsdelivr.net/gh/ThatGuySam/doesitarm@master/README.md"
11 | )
12 |
13 | var (
14 | pattern, _ = regexp.Compile(`\* \[(.*?)\]\((.*?)\) - (✅|✳️|⏹|🚫|🔶)`)
15 |
16 | infoCache map[string]AppInfo = make(map[string]AppInfo)
17 | )
18 |
19 | // AppInfo contains information of an app obtained from remote sources.
20 | type AppInfo struct {
21 | Website string
22 | ArmSupport Support
23 | }
24 |
25 | // Init loads the list of apps that supports Apple Silicon from Does it ARM.
26 | func Init() error {
27 | resp, err := http.Get(sourceURL)
28 | if err != nil {
29 | return err
30 | }
31 | defer resp.Body.Close()
32 |
33 | body, err := ioutil.ReadAll(resp.Body)
34 | if err != nil {
35 | return err
36 | }
37 |
38 | matches := pattern.FindAllStringSubmatch(string(body), -1)
39 |
40 | for _, match := range matches {
41 | name := match[1]
42 | info := AppInfo{
43 | Website: match[2],
44 | }
45 | info.ArmSupport.Parse(match[3])
46 | infoCache[name] = info
47 | }
48 |
49 | return nil
50 | }
51 |
52 | // GetInfo returns the info of an app from remote sources, given the app name.
53 | func GetInfo(name string) (AppInfo, error) {
54 | if info, ok := infoCache[name]; ok {
55 | return info, nil
56 | }
57 | return AppInfo{}, ErrNotFound
58 | }
59 |
--------------------------------------------------------------------------------
/internal/remotecheck/remote_test.go:
--------------------------------------------------------------------------------
1 | package remotecheck
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestInit(t *testing.T) {
10 | err := Init()
11 | assert.Nil(t, err)
12 | }
13 |
14 | func TestGetInfo(t *testing.T) {
15 | err := Init()
16 | assert.Nil(t, err)
17 |
18 | info, err := GetInfo("Go (golang)")
19 | assert.Nil(t, err)
20 | assert.Equal(t, SupportNative, info.ArmSupport)
21 |
22 | info, err = GetInfo("nonexist-app")
23 | assert.NotNil(t, err)
24 | assert.Equal(t, ErrNotFound, err)
25 | assert.Empty(t, info)
26 | }
27 |
--------------------------------------------------------------------------------
/internal/remotecheck/support.go:
--------------------------------------------------------------------------------
1 | package remotecheck
2 |
3 | // Support represents the Arm support status of an app.
4 | type Support uint
5 |
6 | const (
7 | // SupportUndefined is the zero value of Support type.
8 | SupportUndefined Support = iota // zero value
9 | // SupportNative means an app have native Apple Silicon support.
10 | SupportNative
11 | // SupportTransition means an app is supported vis Rosetta 2 or Virtual Environment.
12 | SupportTransition
13 | // SupportInDevelopment means an app does not support Apple Silicon yet but the support is in development.
14 | SupportInDevelopment
15 | // SupportNotYet means an app does not support Apple Silicon.
16 | SupportNotYet
17 | // SupportUnknown means it's not known if an app supports Apple Silicon.
18 | SupportUnknown
19 | )
20 |
21 | // Parse parses support information from string.
22 | func (s *Support) Parse(str string) Support {
23 | switch str {
24 | case "✅":
25 | *s = SupportNative
26 |
27 | case "✳️":
28 | *s = SupportTransition
29 |
30 | case "⏹":
31 | *s = SupportInDevelopment
32 |
33 | case "🚫":
34 | *s = SupportNotYet
35 |
36 | case "🔶":
37 | *s = SupportUnknown
38 |
39 | }
40 |
41 | return *s
42 | }
43 |
44 | func (s Support) String() string {
45 | switch s {
46 | case SupportNative:
47 | return "Supported"
48 |
49 | case SupportTransition:
50 | return "Supported*"
51 |
52 | case SupportInDevelopment:
53 | return "Unsupported"
54 |
55 | case SupportNotYet:
56 | return "Unsupported"
57 |
58 | case SupportUnknown:
59 | return "Unknown"
60 |
61 | default:
62 | return "Unknown"
63 |
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/internal/remotecheck/support_test.go:
--------------------------------------------------------------------------------
1 | package remotecheck
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestSupport_Parse(t *testing.T) {
10 | tests := []struct {
11 | in string
12 | expected Support
13 | }{
14 | {"✅", SupportNative},
15 | {"✳️", SupportTransition},
16 | {"⏹", SupportInDevelopment},
17 | {"🚫", SupportNotYet},
18 | {"🔶", SupportUnknown},
19 | {"some other", SupportUndefined},
20 | }
21 |
22 | for _, test := range tests {
23 | var support Support
24 | actual := support.Parse(test.in)
25 |
26 | assert.Equal(t, test.expected, support)
27 | assert.Equal(t, test.expected, actual)
28 | }
29 | }
30 |
31 | func TestSupport_String(t *testing.T) {
32 | tests := []struct {
33 | support Support
34 | expected string
35 | }{
36 | {SupportNative, "Supported"},
37 | {SupportTransition, "Supported*"},
38 | {SupportInDevelopment, "Unsupported"},
39 | {SupportNotYet, "Unsupported"},
40 | {SupportUnknown, "Unknown"},
41 | {SupportUndefined, "Unknown"},
42 | }
43 |
44 | for _, test := range tests {
45 | actual := test.support.String()
46 | assert.Equal(t, test.expected, actual)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 |
8 | "github.com/harryzcy/ascheck/internal/macapp"
9 | "github.com/harryzcy/ascheck/internal/output"
10 | "github.com/harryzcy/ascheck/internal/remotecheck"
11 | "github.com/urfave/cli/v2"
12 | )
13 |
14 | // handleErr prints error and calls os.Exit(1) if err is not nil.
15 | func handleErr(err error) {
16 | if err != nil {
17 | fmt.Println(err)
18 | os.Exit(1)
19 | }
20 | }
21 |
22 | func main() {
23 | app := &cli.App{
24 | Usage: "A cli app that check app's Apple Silicon support",
25 | Version: "0.2.0",
26 | HideHelpCommand: true,
27 | UsageText: "ascheck [global options]",
28 | Flags: []cli.Flag{
29 | &cli.BoolFlag{
30 | Name: "json",
31 | Usage: "output in json format",
32 | },
33 | },
34 | Action: func(c *cli.Context) error {
35 | err := remotecheck.Init()
36 | handleErr(err)
37 |
38 | apps, err := macapp.GetAllApplications(nil)
39 | handleErr(err)
40 |
41 | if c.Bool("json") {
42 | output.JSON(apps)
43 | } else {
44 | output.Table(apps)
45 | }
46 | return nil
47 | },
48 | }
49 |
50 | err := app.Run(os.Args)
51 | if err != nil {
52 | log.Fatal(err)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/test/data/bash.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
--------------------------------------------------------------------------------
/test/data/env_bash.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
--------------------------------------------------------------------------------
/test/data/env_invalid.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env invalid
2 |
--------------------------------------------------------------------------------
/test/data/example_fat.app/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleExecutable
6 | example
7 | CFBundleIdentifier
8 | com.example.io
9 | CFBundleName
10 | Test App
11 | CFBundleIconFile
12 | main.icns
13 | CFBundleShortVersionString
14 | $PKG_VERSION
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundlePackageType
18 | APPL
19 | IFMajorVersion
20 | 0
21 | IFMinorVersion
22 | 1
23 | NSSupportsAutomaticGraphicsSwitching
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/test/data/example_fat.app/Contents/MacOS/example:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harryzcy/ascheck/32ebf24dcd6795f943e00882fe4050ea4128410f/test/data/example_fat.app/Contents/MacOS/example
--------------------------------------------------------------------------------
/test/data/example_macho.app/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleExecutable
6 | example
7 | CFBundleIdentifier
8 | com.example.io
9 | CFBundleName
10 | Test App
11 | CFBundleIconFile
12 | main.icns
13 | CFBundleShortVersionString
14 | $PKG_VERSION
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundlePackageType
18 | APPL
19 | IFMajorVersion
20 | 0
21 | IFMinorVersion
22 | 1
23 | NSSupportsAutomaticGraphicsSwitching
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/test/data/example_macho.app/Contents/MacOS/example:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harryzcy/ascheck/32ebf24dcd6795f943e00882fe4050ea4128410f/test/data/example_macho.app/Contents/MacOS/example
--------------------------------------------------------------------------------
/test/data/invalid_interpreter.app/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleExecutable
6 | run.sh
7 | CFBundleIdentifier
8 | com.example.io
9 | CFBundleName
10 | Test App
11 | CFBundleIconFile
12 | main.icns
13 | CFBundleShortVersionString
14 | $PKG_VERSION
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundlePackageType
18 | APPL
19 | IFMajorVersion
20 | 0
21 | IFMinorVersion
22 | 1
23 | NSSupportsAutomaticGraphicsSwitching
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/test/data/invalid_interpreter.app/Contents/MacOS/run.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env invalid
2 |
--------------------------------------------------------------------------------
/test/data/invalid_plist.app/Contents/Info.plist:
--------------------------------------------------------------------------------
1 | invalid
2 |
--------------------------------------------------------------------------------
/test/data/invalid_shebang.sh:
--------------------------------------------------------------------------------
1 | invalid
2 |
--------------------------------------------------------------------------------
/test/data/sh_app.app/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleExecutable
6 | run.sh
7 | CFBundleIdentifier
8 | com.example.io
9 | CFBundleName
10 | Test App
11 | CFBundleIconFile
12 | main.icns
13 | CFBundleShortVersionString
14 | $PKG_VERSION
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundlePackageType
18 | APPL
19 | IFMajorVersion
20 | 0
21 | IFMinorVersion
22 | 1
23 | NSSupportsAutomaticGraphicsSwitching
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/test/data/sh_app.app/Contents/MacOS/run.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
--------------------------------------------------------------------------------
/test/data/unknown_type.app/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleExecutable
6 | example
7 | CFBundleIdentifier
8 | com.example.io
9 | CFBundleName
10 | Test App
11 | CFBundleIconFile
12 | main.icns
13 | CFBundleShortVersionString
14 | $PKG_VERSION
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundlePackageType
18 | APPL
19 | IFMajorVersion
20 | 0
21 | IFMinorVersion
22 | 1
23 | NSSupportsAutomaticGraphicsSwitching
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------