Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 92 additions & 3 deletions .github/workflows/cmake-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,43 @@ jobs:
working-directory: ${{github.workspace}}/build
run: ./tests

build_and_test_windows:
runs-on: windows-2022
timeout-minutes: 180
env:
BUILD_TYPE: RelWithDebInfo

steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: MSVC environment
uses: ilammy/msvc-dev-cmd@v1

- name: Setup LLVM
uses: ZhongRuoyu/setup-llvm@v0
with:
llvm-version: 20

- name: Install Ninja
run: choco install ninja --yes --no-progress

- name: Configure CMake
shell: pwsh
run: |
cmake -B build -G Ninja `
"-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }}" `
"-DCMAKE_C_COMPILER=$env:LLVM_PATH\bin\clang.exe" `
"-DCMAKE_CXX_COMPILER=$env:LLVM_PATH\bin\clang++.exe" `
"-DLLVM_DIR=$env:LLVM_PATH\lib\cmake\llvm"

- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{ env.BUILD_TYPE }} --target tests

- name: Test
working-directory: ${{github.workspace}}/build
run: .\tests.exe

# the version the release binaries are stamped with and the tag the release gets. Resolved once, so
# both platforms and the release itself cannot disagree about what was built
#
Expand All @@ -77,7 +114,7 @@ jobs:
# and no commit pushed back onto master
resolve_version:
runs-on: ubuntu-24.04
needs: build_and_test
needs: [build_and_test, build_and_test_windows]
outputs:
version: ${{ steps.resolve.outputs.version }}
patch: ${{ steps.resolve.outputs.patch }}
Expand Down Expand Up @@ -416,11 +453,60 @@ jobs:
name: ${{env.EPM_BIN_NAME}}
path: ${{github.workspace}}/${{env.EPM_BIN_NAME}}.tar.gz

build_release_windows_x86_64:
runs-on: windows-2022
timeout-minutes: 180
needs: resolve_version
env:
BUILD_TYPE: Release
BIN_NAME: echo-windows-x86_64
EPM_BIN_NAME: epm-windows-x86_64
VERSION: ${{needs.resolve_version.outputs.version}}${{needs.resolve_version.outputs.suffix}}
ECO_VERSION_PATCH: ${{needs.resolve_version.outputs.patch}}
ECO_VERSION_SUFFIX: ${{needs.resolve_version.outputs.suffix}}

steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: MSVC environment
uses: ilammy/msvc-dev-cmd@v1

- name: Setup LLVM
uses: ZhongRuoyu/setup-llvm@v0
with:
llvm-version: 20

- name: Install Ninja and Inno Setup
run: choco install ninja innosetup --yes --no-progress

- name: Build the Windows release
shell: pwsh
run: ./tools/windows/build_release.ps1

- name: Upload release archive
uses: actions/upload-artifact@v4
with:
name: ${{env.BIN_NAME}}
path: ${{github.workspace}}/${{env.BIN_NAME}}.zip

- name: Upload epm archive
uses: actions/upload-artifact@v4
with:
name: ${{env.EPM_BIN_NAME}}
path: ${{github.workspace}}/${{env.EPM_BIN_NAME}}.zip

- name: Upload setup wizard
uses: actions/upload-artifact@v4
with:
name: echo-windows-x86_64-setup
path: ${{github.workspace}}/echo-windows-x86_64-setup.exe

# every push to master is a patch release. `gh release create` creates the tag itself, so nothing
# here pushes a ref by hand and nothing commits back onto the branch
release:
runs-on: ubuntu-24.04
needs: [resolve_version, build_release, build_release_macos_arm64]
needs: [resolve_version, build_release, build_release_macos_arm64, build_release_windows_x86_64]
if: github.ref == 'refs/heads/master' && github.event_name == 'push'

steps:
Expand Down Expand Up @@ -455,5 +541,8 @@ jobs:
--generate-notes \
artifacts/echo-linux-x86_64/echo-linux-x86_64.tar.gz \
artifacts/echo-macos-arm64/echo-macos-arm64.tar.gz \
artifacts/echo-windows-x86_64/echo-windows-x86_64.zip \
artifacts/epm-linux-x86_64/epm-linux-x86_64.tar.gz \
artifacts/epm-macos-arm64/epm-macos-arm64.tar.gz
artifacts/epm-macos-arm64/epm-macos-arm64.tar.gz \
artifacts/epm-windows-x86_64/epm-windows-x86_64.zip \
artifacts/echo-windows-x86_64-setup/echo-windows-x86_64-setup.exe
112 changes: 101 additions & 11 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,20 @@ set(LIBNAME ${APPNAME}_lib)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -g")
set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fsanitize=address")

set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
# ASan and the GCC-style debug flags are a Unix host's. MSVC (and clang-cl) reject
# `-fsanitize=address` / `-O0 -g` as unknown options, and a Debug configure on
# Windows would otherwise fail before compiling a single file. clang++ with the
# GNU driver still takes them; it is `MSVC` that is the split, not `WIN32`.
if(NOT MSVC)
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -fsanitize=address")
set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fsanitize=address")
endif()
# MSVC otherwise reads this tree as the system ANSI code page, so a UTF-8 box-drawing literal in
# CommandLineHelp.cpp is three CP1252 characters rather than one glyph. clang defaults to UTF-8;
# this is the flag that makes cl.exe match. directory-wide so a target added later cannot forget it
if(MSVC)
add_compile_options(/utf-8)
endif()
# set(CMAKE_BUILD_TYPE Debug)

set(VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/")
Expand All @@ -26,6 +36,50 @@ message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})

# Official LLVM Windows packages record the DIA SDK from the machine that built
# them - VS Enterprise on the release builders. That path is a file-level Ninja
# input, so a Community/Professional install fails at build time with "missing
# and no known rule" rather than a link error. Rewrite it onto whichever 2022
# edition actually has diaguids.lib here.
if(WIN32)
unset(ECO_DIA_GUIDS)
foreach(ECO_VS_EDITION Enterprise Professional Community BuildTools)
set(ECO_DIA_CANDIDATE "C:/Program Files/Microsoft Visual Studio/2022/${ECO_VS_EDITION}/DIA SDK/lib/amd64/diaguids.lib")
if(EXISTS "${ECO_DIA_CANDIDATE}")
set(ECO_DIA_GUIDS "${ECO_DIA_CANDIDATE}")
break()
endif()
endforeach()

if(ECO_DIA_GUIDS)
foreach(ECO_LLVM_LIB IN LISTS LLVM_AVAILABLE_LIBS)
if(NOT TARGET ${ECO_LLVM_LIB})
continue()
endif()

get_target_property(ECO_LLVM_LIB_DEPS ${ECO_LLVM_LIB} INTERFACE_LINK_LIBRARIES)
if(NOT ECO_LLVM_LIB_DEPS)
continue()
endif()

set(ECO_LLVM_LIB_NEW_DEPS "")
set(ECO_LLVM_LIB_REPLACED FALSE)
foreach(ECO_DEP IN LISTS ECO_LLVM_LIB_DEPS)
if(ECO_DEP MATCHES "DIA SDK/.*/diaguids\\.lib")
list(APPEND ECO_LLVM_LIB_NEW_DEPS "${ECO_DIA_GUIDS}")
set(ECO_LLVM_LIB_REPLACED TRUE)
else()
list(APPEND ECO_LLVM_LIB_NEW_DEPS "${ECO_DEP}")
endif()
endforeach()

if(ECO_LLVM_LIB_REPLACED)
set_property(TARGET ${ECO_LLVM_LIB} PROPERTY INTERFACE_LINK_LIBRARIES "${ECO_LLVM_LIB_NEW_DEPS}")
endif()
endforeach()
endif()
endif()

# DO NOT INCLUDE! THIS CAUSED AN ERROR THAT TOOK ME 3 FULL DAYS TO PIN DOWN
# I leave this here as a warning to future me and as a totem to ward off evil spirits
# honestly I do not even rember why I added this in the first place, it in combination
Expand All @@ -35,10 +89,18 @@ include_directories(${LLVM_INCLUDE_DIRS})

# Echo
file(GLOB_RECURSE SOURCES "src/*.cpp")
set_source_files_properties(${SOURCES} PROPERTIES COMPILE_FLAGS "-Wall -Wextra -pedantic -Wno-unused-parameter")
if(NOT MSVC)
set_source_files_properties(${SOURCES} PROPERTIES COMPILE_FLAGS "-Wall -Wextra -pedantic -Wno-unused-parameter")
endif()

# the library is what the tests link. `main.cpp` is the driver, and two `main`s in one
# image is lld picking the compiler's: Catch2 never runs and `--list-tests` is refused
# as an echoc flag. the executable still compiles the driver in
set(LIB_SOURCES ${SOURCES})
list(FILTER LIB_SOURCES EXCLUDE REGEX "[\\/]main\\.cpp$")

add_executable(${APPNAME} ${SOURCES})
add_library(${LIBNAME} STATIC ${SOURCES})
add_library(${LIBNAME} STATIC ${LIB_SOURCES})

# where the stdlib sources live. PRIVATE on each target that needs it rather than PUBLIC on the
# library, because a consumer of echoc_lib is not thereby a consumer of the source tree - so the three
Expand All @@ -48,6 +110,25 @@ set(ECO_STDLIB_DEFINITION "STDLIB_SOURCE_DIR=\"${CMAKE_SOURCE_DIR}/stdlib\"")
target_compile_definitions(${APPNAME} PRIVATE ${ECO_STDLIB_DEFINITION})
target_compile_definitions(${LIBNAME} PRIVATE ${ECO_STDLIB_DEFINITION})

# the clang that compiled this binary. `echoc build` / `echoc test` must not
# pick a GNU-ABI clang that happens to sit first on PATH (llvm-mingw cannot
# link the MSVC objects we emit)
set(ECO_HOST_CLANG "${CMAKE_C_COMPILER}")
if(ECO_HOST_CLANG MATCHES "clang\\+\\+")
get_filename_component(ECO_HOST_CLANG_DIR "${ECO_HOST_CLANG}" DIRECTORY)
if(WIN32)
set(ECO_HOST_CLANG_CANDIDATE "${ECO_HOST_CLANG_DIR}/clang.exe")
else()
set(ECO_HOST_CLANG_CANDIDATE "${ECO_HOST_CLANG_DIR}/clang")
endif()
if(EXISTS "${ECO_HOST_CLANG_CANDIDATE}")
set(ECO_HOST_CLANG "${ECO_HOST_CLANG_CANDIDATE}")
endif()
endif()
file(TO_CMAKE_PATH "${ECO_HOST_CLANG}" ECO_HOST_CLANG_DEF)
target_compile_definitions(${APPNAME} PRIVATE "ECO_HOST_CLANG=\"${ECO_HOST_CLANG_DEF}\"")
target_compile_definitions(${LIBNAME} PRIVATE "ECO_HOST_CLANG=\"${ECO_HOST_CLANG_DEF}\"")

# what a *released* binary is built with. the path above is the build machine's, so a binary someone
# downloaded can only find the standard library if it carries it - see the comment on
# ECO_USE_EMBEDDED_STDLIB in include/eco.h. Off by default: a local build wants the stdlib it can edit,
Expand Down Expand Up @@ -143,14 +224,18 @@ set(LLVM_COMPONENTS
)

# add the native architecture to the list of components
if (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
#
# Windows reports AMD64 where Unix says x86_64. compared case-insensitively so
# a generator that spells `AMD64` and one that spells `amd64` are the same row
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" ECO_PROCESSOR)
if (ECO_PROCESSOR MATCHES "x86_64|amd64")
set(NATIVE_ARCH "X86")
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64")
elseif (ECO_PROCESSOR MATCHES "arm64|aarch64")
# Darwin says arm64, Linux says aarch64. both are AArch64 to LLVM
set(NATIVE_ARCH "AArch64")
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "arm")
elseif (ECO_PROCESSOR MATCHES "arm")
set(NATIVE_ARCH "ARM")
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "riscv64")
elseif (ECO_PROCESSOR MATCHES "riscv64")
set(NATIVE_ARCH "RISCV")
else()
message(FATAL_ERROR "Unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}")
Expand Down Expand Up @@ -225,4 +310,9 @@ target_compile_definitions(tests PRIVATE
enable_testing()
list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras)
include(Catch)
catch_discover_tests(tests)
# put the LLVM we built against first on PATH for each ctest invocation. Catch2
# passes TEST_CASE names as argv, so a name starting with `-` is a Catch2 flag
# (`-o` writes a reporter file and then runs the *entire* suite, `--help` prints
# usage). those cases are named with backticks instead, e.g. `` `-o` overrides ``
get_filename_component(ECO_LLVM_BIN "${ECO_HOST_CLANG}" DIRECTORY)
catch_discover_tests(tests DL_PATHS "${ECO_LLVM_BIN}")
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ Echo won't run PHP right now, nor in the future. Echo is fundamentally different

## Installation

macOS / Linux:

```bash
curl -fsSL https://raw.githubusercontent.com/echolang/echo/master/install.sh | bash
```

`echoc run hello.eco` to jit stuff.
Windows (PowerShell):

```powershell
irm https://raw.githubusercontent.com/echolang/echo/master/install.ps1 | iex
```

Or download `echo-windows-x86_64-setup.exe` from the [latest release](https://github.com/echolang/echo/releases/latest) and run the wizard. The zip is there if you want to unpack it yourself.

Compiling to a native `echoc build` needs `clang` on your `PATH`; on macOS that means the Xcode command line tools (`xcode-select --install`).
`echoc run hello.eco` to jit. `echoc build hello.eco` emits a native binary. On macOS that needs the Xcode command line tools (`xcode-select --install`). On Windows the installer already ships clang, lld-link and a sysroot; no separate LLVM install.
2 changes: 1 addition & 1 deletion epm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A package manager for Echo. It resolves, fetches and vendors. It never compiles
anything. It writes a `module.eco` only as a whole new file (`epm init`) or as
the one `#[requires:]` line `epm add` was asked for.

A released `epm` arrives next to `echoc` from the install one-liner. This tree
A released `epm` arrives next to `echoc` from the installer. This tree
is the bootstrap: path-depends on `../../echolibs/libjson` and
`../../echolibs/libcurl`, compiled by this tree's `echoc`.

Expand Down
Loading
Loading