diff --git a/.github/workflows/e2e_test_darwin_prebuilt.yaml b/.github/workflows/e2e_test_darwin_prebuilt.yaml new file mode 100644 index 0000000..41c67da --- /dev/null +++ b/.github/workflows/e2e_test_darwin_prebuilt.yaml @@ -0,0 +1,50 @@ +name: E2E tests on Darwin MacOS (Prebuilt release) + +on: + push: + # branches: [main] + pull_request: + workflow_dispatch: + schedule: + # Runs at 12am UTC + - cron: '0 0 * * *' + +jobs: + e2e_tests: + strategy: + matrix: + # ref: https://github.com/actions/runner-images + os: [macos-13] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '^1.24.2' # The Go version to download (if necessary) and use. + + - name: build vfox (MacOS) + run: | + git clone https://github.com/version-fox/vfox.git + cd vfox + go build -o vfox + chmod +x vfox + cp vfox /usr/local/bin + + - name: add vfox-erlang plugin (Unix-like) + run: | + vfox -version + vfox add --source https://github.com/version-fox/vfox-erlang/archive/${GITHUB_REF}.zip erlang + + - name: install Erlang/OTP by vfox-erlang plugin (Darwin prebuilt) + run: | + export USE_PREBUILT_OTP=true + vfox install erlang@26.2.3 + vfox use -g erlang@26.2.3 + eval "$(vfox activate bash)" + echo "===============PATH===============" + echo $PATH + echo "===============PATH===============" + cd assets + erlc hello.erl + erl -noshell -s hello hello_world -s init stop \ No newline at end of file diff --git a/.github/workflows/update_otp_versions.yaml b/.github/workflows/update_otp_versions.yaml index 124db71..b54fbfd 100644 --- a/.github/workflows/update_otp_versions.yaml +++ b/.github/workflows/update_otp_versions.yaml @@ -21,6 +21,7 @@ jobs: cd assets python -m pip install requests python get_all_otp_versions.py + python get_macos_prebuilt_versions.py - name: Push updated version file run: | diff --git a/.gitignore b/.gitignore index 446b731..521bdad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea .tool-versions *.beam -available.cache \ No newline at end of file +available.cache +.available.cache \ No newline at end of file diff --git a/README.md b/README.md index 8f5d718..ecff234 100644 --- a/README.md +++ b/README.md @@ -85,15 +85,32 @@ You can reference the E2E test in in windows-2022: [.github/workflows/e2e_test_w ## install a prebuilt Erlang/OTP version -After vfox-erlang v1.1.0, you can also install a prebuilt Erlang/OTP version in Ubuntu linux system. +After vfox-erlang v1.1.0, you can also install a prebuilt Erlang/OTP version in Ubuntu linux system and MacOS. **Before install, you must disable vfox search cache.** Reference: [https://vfox.lhan.me/guides/configuration.html#cache-settings](https://vfox.lhan.me/guides/configuration.html#cache-settings) +### Linux (Ubuntu) + This is an installation example in Bash Shell: ```shell # install an available version, you can also a avaliable version in: https://bobs-list.kobrakai.de/ USE_PREBUILT_OTP="ubuntu-20.04" vfox search erlang +USE_PREBUILT_OTP="ubuntu-20.04" vfox install erlang@26.2.3 +``` + +**USE_PREBUILT_OTP** var value for Linux is one of: ["ubuntu-14.04", "ubuntu-16.04", "ubuntu-18.04", "ubuntu-20.04", "ubuntu-22.04", "ubuntu-24.04"]. + +### MacOS + +For MacOS, you can use prebuilt Erlang/OTP versions from [@erlef/otp_builds](https://github.com/erlef/otp_builds) by setting the `USE_PREBUILT_OTP` environment variable: + +```shell +# install a prebuilt version for MacOS (automatically detects architecture) +USE_PREBUILT_OTP=true vfox search erlang +USE_PREBUILT_OTP=true vfox install erlang@26.2.3 ``` -**USE_PREBUILT_OTP** var value is one of: ["ubuntu-14.04", "ubuntu-16.04", "ubuntu-18.04", "ubuntu-20.04", "ubuntu-22.04", "ubuntu-24.04"]. \ No newline at end of file +**USE_PREBUILT_OTP** can be set to any non-empty value (e.g., "1", "true", "macos") to enable prebuilt mode. + +Supported architectures: amd64, x86_64, arm64, aarch64. macOS uses a dedicated prebuilt version list that differs from the Ubuntu prebuilt versions. diff --git a/assets/get_all_otp_versions.py b/assets/get_all_otp_versions.py index 7e7a7f6..c39afe4 100644 --- a/assets/get_all_otp_versions.py +++ b/assets/get_all_otp_versions.py @@ -1,23 +1,27 @@ import json import requests + # fetch version: -> https://api.github.com/repos/erlang/otp/tags?per_page=100&sort=pushed # github api has rate limt: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repository-tags # prefer use local version file def update_all_version_from_github_api(): all_version = [] - for page in range(1,10): + for page in range(1, 10): url = f"https://api.github.com/repos/erlang/otp/tags?per_page=100&sort=pushed&page={page}" response = requests.get(url) data = response.json() - all_version = all_version + data + all_version.extend(data) if response.status_code != 200: print("Failed to fetch data from github api") return - with open("erlang_otp_versions_from_gtihub_api.json", 'w', encoding="utf-8") as file: + with open( + "erlang_otp_versions_from_gtihub_api.json", "w", encoding="utf-8" + ) as file: json.dump(all_version, file, indent=4) + def get_all_prebuilt_version_from_bob(): # ALLOW_OS_RELEASE = ["ubuntu-14.04", "ubuntu-16.04", "ubuntu-18.04", "ubuntu-20.04", "ubuntu-22.04", "ubuntu-24.04"] ALLOW_OS_RELEASE = ["ubuntu-20.04"] @@ -25,17 +29,88 @@ def get_all_prebuilt_version_from_bob(): for release in ALLOW_OS_RELEASE: url = f"https://builds.hex.pm/builds/otp/{release}/builds.txt" response = requests.get(url) - all_version_info = response.text.split("\n") - + all_version_info.extend(response.text.split("\n")) + all_prebuilt_versions = [] for version in all_version_info: if version.split(" ")[0]: all_prebuilt_versions.append(version.split(" ")[0]) return all_prebuilt_versions + +def get_macos_prebuilt_versions(): + """Fetch all available macOS prebuilt versions from erlef/otp_builds""" + + # GitHub API URL for releases + api_url = "https://api.github.com/repos/erlef/otp_builds/releases" + + all_versions = [] + page = 1 + + try: + while True: + print(f"Fetching macOS prebuilt versions page {page}...") + url = f"{api_url}?page={page}&per_page=100" + + response = requests.get(url) + if response.status_code != 200: + break + + releases = response.json() + if not releases: + break + + for release in releases: + tag_name = release["tag_name"] + assets = release["assets"] + + # Check if there are macOS related assets + has_macos_assets = False + for asset in assets: + asset_name = asset["name"] + # Check if contains macOS related filename patterns + if any( + pattern in asset_name.lower() + for pattern in [ + "macos", + "darwin", + "osx", + "aarch64-apple", + "x86_64-apple", + "arm64.tar.gz", + "amd64.tar.gz", + ] + ): + has_macos_assets = True + break + + if has_macos_assets: + # Remove 'OTP-' prefix except for special versions like master-latest + processed_version = tag_name + if processed_version.startswith("OTP-"): + processed_version = processed_version[ + 4: + ] # Remove 'OTP-' prefix + all_versions.append(processed_version) + print(f"Found macOS version: {tag_name} -> {processed_version}") + + page += 1 + # Limit maximum pages to avoid infinite loop + if page > 20: + break + + except Exception as e: + print(f"Error fetching macOS prebuilt data: {e}") + return [] + + return all_versions + + def get_all_version(): version_set = set() - with open("erlang_otp_versions_from_gtihub_api.json", 'r', encoding="utf-8") as file: + with open( + "erlang_otp_versions_from_gtihub_api.json", "r", encoding="utf-8" + ) as file: data = json.load(file) for item in data: if "OTP-" not in item["tarball_url"]: @@ -44,16 +119,33 @@ def get_all_version(): version_set.add(version) return version_set + if __name__ == "__main__": update_all_version_from_github_api() versions = list(get_all_version()) versions.sort(reverse=True) # print(versions) - with open("versions.txt", 'w') as file: + with open("versions.txt", "w") as file: for version in versions: - file.write(version + '\n') - with open("prebuilt_versions.txt", 'w') as file: + file.write(version + "\n") + + # Generate prebuilt versions for Linux + with open("prebuilt_versions.txt", "w") as file: prebuilt_versions = get_all_prebuilt_version_from_bob() prebuilt_versions.sort(reverse=True) for version in prebuilt_versions: - file.write(version + '\n') \ No newline at end of file + file.write(version + "\n") + + # Generate prebuilt versions for macOS + print("\nFetching macOS prebuilt versions...") + macos_versions = get_macos_prebuilt_versions() + if macos_versions: + macos_versions.sort(reverse=True) + with open("macos_prebuilt_versions.txt", "w", encoding="utf-8") as file: + for version in macos_versions: + file.write(version + "\n") + print( + f"Found {len(macos_versions)} macOS prebuilt versions, saved to macos_prebuilt_versions.txt" + ) + else: + print("No macOS prebuilt versions found.") diff --git a/assets/macos_prebuilt_versions.txt b/assets/macos_prebuilt_versions.txt new file mode 100644 index 0000000..a7a9dd8 --- /dev/null +++ b/assets/macos_prebuilt_versions.txt @@ -0,0 +1,99 @@ +master-latest +maint-latest +maint-27-latest +maint-26-latest +maint-25-latest +28.0.1 +28.0-rc4 +28.0-rc3 +28.0-rc2 +28.0-rc1 +28.0 +27.3.4.1 +27.3.4 +27.3.3 +27.3.2 +27.3.1 +27.3 +27.2.4 +27.2.3 +27.2.2 +27.2.1 +27.2 +27.1.3 +27.1.2 +27.1.1 +27.1 +27.0.1 +27.0-rc3 +27.0-rc2 +27.0-rc1 +27.0 +26.2.5.9 +26.2.5.8 +26.2.5.7 +26.2.5.6 +26.2.5.5 +26.2.5.4 +26.2.5.3 +26.2.5.2 +26.2.5.13 +26.2.5.12 +26.2.5.11 +26.2.5.10 +26.2.5.1 +26.2.5 +26.2.4 +26.2.3 +26.2.2 +26.2.1 +26.2 +26.1.2 +26.1.1 +26.1 +26.0.2 +26.0.1 +26.0-rc3 +26.0-rc2 +26.0-rc1 +26.0 +25.3.2.9 +25.3.2.8 +25.3.2.7 +25.3.2.6 +25.3.2.5 +25.3.2.4 +25.3.2.3 +25.3.2.21 +25.3.2.20 +25.3.2.2 +25.3.2.19 +25.3.2.18 +25.3.2.17 +25.3.2.16 +25.3.2.15 +25.3.2.14 +25.3.2.13 +25.3.2.12 +25.3.2.11 +25.3.2.10 +25.3.2.1 +25.3.2 +25.3.1 +25.3 +25.2.3 +25.2.2 +25.2.1 +25.2 +25.1.2.1 +25.1.2 +25.1.1 +25.1 +25.0.4 +25.0.3 +25.0.2 +25.0.1 +25.0-rc3 +25.0-rc2 +25.0-rc1 +25.0 diff --git a/hooks/post_install.lua b/hooks/post_install.lua index 09c4d15..3769552 100644 --- a/hooks/post_install.lua +++ b/hooks/post_install.lua @@ -28,12 +28,25 @@ function PLUGIN:PostInstall(ctx) elseif PRE_BUILT_OS_RELEASE then print("Erlang/OTP install from prebuilt release: " .. PRE_BUILT_OS_RELEASE) local install_path = ctx.sdkInfo.erlang.path - local install_cmd = "cd " .. install_path .. " && ./Install -cross -sasl" - local status = os.execute(install_cmd) - if status ~= 0 then - error( - "Erlang/OTP install failed, please check the stdout for details. Make sure you have the required utilties: https://www.erlang.org/doc/installation_guide/install#required-utilities") + if RUNTIME.osType == "darwin" then + -- For MacOS prebuilts from @erlef/otp_builds, the tarball contains a ready-to-use installation + -- We need to move the contents to a 'release' subdirectory to match expected structure + local setup_cmd = "cd " .. + install_path .. + " && mkdir -p release && for item in *; do if [ \"$item\" != \"release\" ]; then mv \"$item\" release/; fi; done 2>/dev/null || true" + local status = os.execute(setup_cmd) + if status ~= 0 then + error("Erlang/OTP install failed during file organization, please check the stdout for details.") + end + else + -- Linux installation using the Install script + local install_cmd = "cd " .. install_path .. " && ./Install -cross -sasl" + local status = os.execute(install_cmd) + if status ~= 0 then + error( + "Erlang/OTP install failed, please check the stdout for details. Make sure you have the required utilties: https://www.erlang.org/doc/installation_guide/install#required-utilities") + end end else -- use ENV OTP_COMPILE_ARGS to control compile behavior @@ -44,19 +57,19 @@ function PLUGIN:PostInstall(ctx) local docs_target = os.getenv("DOC_TARGETS") or "chunks" print( - "If you enable some Erlang/OTP features, maybe you can reference this guide: https://github.com/erlang/otp/blob/master/HOWTO/INSTALL.md#configuring-1") + "If you enable some Erlang/OTP features, maybe you can reference this guide: https://github.com/erlang/otp/blob/master/HOWTO/INSTALL.md#configuring-1") os.execute("sleep " .. tonumber(3)) local configure_cmd = "cd " .. path .. " && ./configure --prefix=" .. path .. "/release " .. configure_args local install_erlang_cmd = "cd " .. path .. "&& make && make install" -- install with docs chunk for IDE/REPL docs hits & type hits local install_erlang_docs_cmd = "cd " .. - path .. " && make docs DOC_TARGETS=" .. docs_target .. " && make install-docs DOC_TARGETS=" .. docs_target + path .. " && make docs DOC_TARGETS=" .. docs_target .. " && make install-docs DOC_TARGETS=" .. docs_target local status = os.execute(configure_cmd .. " && " .. install_erlang_cmd .. " && " .. install_erlang_docs_cmd) if status ~= 0 then error( - "Erlang/OTP install failed, please check the stdout for details. Make sure you have the required utilties: https://www.erlang.org/doc/installation_guide/install#required-utilities") + "Erlang/OTP install failed, please check the stdout for details. Make sure you have the required utilties: https://www.erlang.org/doc/installation_guide/install#required-utilities") end end end diff --git a/hooks/pre_install.lua b/hooks/pre_install.lua index 6009e6a..fecc726 100644 --- a/hooks/pre_install.lua +++ b/hooks/pre_install.lua @@ -16,22 +16,92 @@ function PLUGIN:PreInstall(ctx) local download_url local PRE_BUILT_OS_RELEASE = erlangUtils.get_config_from_env("USE_PREBUILT_OTP") if RUNTIME.osType == "windows" then + -- For Windows, always use OTP- prefix for regular versions + local win_tag = erlang_version + if not (string.match(erlang_version, "latest$") or string.match(erlang_version, "^maint%-")) then + win_tag = "OTP-" .. erlang_version + end + if RUNTIME.archType == "amd64" then - download_url = "https://github.com/erlang/otp/releases/download/OTP-" .. - erlang_version .. "/otp_win64_" .. erlang_version .. ".exe" + download_url = "https://github.com/erlang/otp/releases/download/" .. + win_tag .. "/otp_win64_" .. erlang_version .. ".exe" else - download_url = "https://github.com/erlang/otp/releases/download/OTP-" .. - erlang_version .. "/otp_win32_" .. erlang_version .. ".exe" + download_url = "https://github.com/erlang/otp/releases/download/" .. + win_tag .. "/otp_win32_" .. erlang_version .. ".exe" end elseif RUNTIME.osType == "linux" and PRE_BUILT_OS_RELEASE then - local SUPPORT_OS_RELEASE = { "ubuntu-14.04", "ubuntu-16.04", "ubuntu-18.04", "ubuntu-20.04", "ubuntu-22.04", "ubuntu-24.04" } + local SUPPORT_OS_RELEASE = { "ubuntu-14.04", "ubuntu-16.04", "ubuntu-18.04", "ubuntu-20.04", "ubuntu-22.04", + "ubuntu-24.04" } if not erlangUtils.array_contains(SUPPORT_OS_RELEASE, PRE_BUILT_OS_RELEASE) then error( "Make sure the USE_PREBUILT environment variable is set to one of the following values: ubuntu-20.04, ubuntu-22.04, ubuntu-24.04") end - download_url = "https://builds.hex.pm/builds/otp/" .. RUNTIME.archType .. "/" .. PRE_BUILT_OS_RELEASE .. "/" .. erlang_version .. ".tar.gz" + download_url = "https://builds.hex.pm/builds/otp/" .. + RUNTIME.archType .. "/" .. PRE_BUILT_OS_RELEASE .. "/" .. erlang_version .. ".tar.gz" + elseif RUNTIME.osType == "darwin" and erlangUtils.get_config_from_env("USE_PREBUILT_OTP") then + -- Use @erlef/otp_builds for MacOS prebuilt binaries + local arch_mapping = { + amd64 = "amd64", + x86_64 = "x86_64", + arm64 = "arm64", + aarch64 = "aarch64" + } + + local mapped_arch = arch_mapping[RUNTIME.archType] + if not mapped_arch then + error("Unsupported architecture for prebuilt MacOS binaries: " .. + RUNTIME.archType .. ". Supported architectures: amd64, x86_64, arm64, aarch64") + end + + -- Prepare tag name and file prefix for download URL + -- Special versions like master-latest, maint-latest etc. don't need OTP- prefix + -- Regular versions like 28.0.1 need OTP- prefix for GitHub tag but not for filename + local tag_name = erlang_version + local file_prefix = erlang_version + + if not (string.match(erlang_version, "latest$") or string.match(erlang_version, "^maint%-")) then + -- Regular version like "28.0.1" -> tag should be "OTP-28.0.1" + tag_name = "OTP-" .. erlang_version + file_prefix = "OTP-" .. erlang_version + end + + -- Use different URL patterns based on architecture + if mapped_arch == "aarch64" then + -- https://github.com/erlef/otp_builds/releases/download/maint-25-latest/otp-aarch64-apple-darwin.tar.gz + download_url = "https://github.com/erlef/otp_builds/releases/download/" .. + tag_name .. "/otp-aarch64-apple-darwin.tar.gz" + elseif mapped_arch == "arm64" then + if string.match(erlang_version, "latest$") or string.match(erlang_version, "^maint%-") then + -- For latest versions: use aarch64 naming for arm64 + download_url = "https://github.com/erlef/otp_builds/releases/download/" .. + tag_name .. "/otp-aarch64-apple-darwin.tar.gz" + else + -- For regular versions: https://github.com/erlef/otp_builds/releases/download/OTP-28.0.1/OTP-28.0.1-macos-arm64.tar.gz + download_url = "https://github.com/erlef/otp_builds/releases/download/" .. + tag_name .. "/" .. file_prefix .. "-macos-arm64.tar.gz" + end + elseif mapped_arch == "amd64" then + if string.match(erlang_version, "latest$") or string.match(erlang_version, "^maint%-") then + -- For latest versions: use x86_64 naming for amd64 + download_url = "https://github.com/erlef/otp_builds/releases/download/" .. + tag_name .. "/otp-x86_64-apple-darwin.tar.gz" + else + -- For regular versions: https://github.com/erlef/otp_builds/releases/download/OTP-28.0.1/OTP-28.0.1-macos-amd64.tar.gz + download_url = "https://github.com/erlef/otp_builds/releases/download/" .. + tag_name .. "/" .. file_prefix .. "-macos-amd64.tar.gz" + end + elseif mapped_arch == "x86_64" then + -- For x86_64, always use the same naming pattern as aarch64 + download_url = "https://github.com/erlef/otp_builds/releases/download/" .. + tag_name .. "/otp-x86_64-apple-darwin.tar.gz" + end else - download_url = "https://github.com/erlang/otp/archive/refs/tags/OTP-" .. erlang_version .. ".tar.gz" + -- For source code download, always use OTP- prefix for regular versions + local source_tag = erlang_version + if not (string.match(erlang_version, "latest$") or string.match(erlang_version, "^maint%-")) then + source_tag = "OTP-" .. erlang_version + end + download_url = "https://github.com/erlang/otp/archive/refs/tags/" .. source_tag .. ".tar.gz" end print("Download Erlang/OTP from " .. download_url) diff --git a/lib/erlang_utils.lua b/lib/erlang_utils.lua index ea7d9e9..b6d5a52 100644 --- a/lib/erlang_utils.lua +++ b/lib/erlang_utils.lua @@ -60,9 +60,17 @@ end function erlang_utils.get_erlang_release_verions() local search_url = "https://fastly.jsdelivr.net/gh/version-fox/vfox-erlang@main/assets/versions.txt" - if erlang_utils.get_config_from_env("USE_PREBUILT_OTP") then - -- search_url = "http://localhost:3000/prebuilt_versions.txt" - search_url = "https://fastly.jsdelivr.net/gh/version-fox/vfox-erlang@main/assets/prebuilt_versions.txt" + -- Use prebuilt versions when USE_PREBUILT_OTP is explicitly set + local use_prebuilt = erlang_utils.get_config_from_env("USE_PREBUILT_OTP") + if use_prebuilt then + -- Choose different prebuilt version lists based on platform + if RUNTIME.osType == "darwin" then + -- Use macOS specific prebuilt versions + search_url = "https://fastly.jsdelivr.net/gh/version-fox/vfox-erlang@main/assets/macos_prebuilt_versions.txt" + else + -- Use Ubuntu prebuilt versions for Linux + search_url = "https://fastly.jsdelivr.net/gh/version-fox/vfox-erlang@main/assets/prebuilt_versions.txt" + end end local resp, err = http.get({