Skip to content

Commit

Permalink
Set the DMD environment variable for all compilers
Browse files Browse the repository at this point in the history
This action sets up DC to point to the compiler. It is not much more
work to also set DMD to point to the dmd wrapper of the compiler. dmd
is its own wrapper, ldmd2 comes by default with ldc2. The only one
left out is gdc.

The action allowed installing gdc, either alone or with gdmd and
setting DC to point to one of them. This required the user to specify
which what they wanted to set DC to. This is pointlessly complex as
gdmd is just a script file that can be easily downloaded. The previous
decision to do this separately was because of the installation method
of the gdmd script, it defaulting to installing it through `apt`,
which would likely lead to many more useless packages being
installed. This is too much work for just a script file so it will now
always be installed by fetching it from the upstream repo.  See
dlang-community#77 (comment)
for a discussion around this topic.

This has as consequences that the `gdmd` and `gdmd-<version>` inputs
to `dc` become invalid and that the `gdmd_sha` inputs becomes
mandatory. To accommodate for the second issue the action now picks a
default value for `gdmd_sha`.

Signed-off-by: Andrei Horodniceanu <[email protected]>
  • Loading branch information
the-horo committed Jul 11, 2024
1 parent c9aa887 commit ee7eada
Show file tree
Hide file tree
Showing 13 changed files with 253 additions and 153 deletions.
4 changes: 4 additions & 0 deletions .github/actions/verify-d-compiler/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ runs:
shell: bash
run: $DC .github/hello.d && ./hello

- name: Verify D Compiler dmd-wrapper ($DMD)
shell: bash
run: $DMD -run .github/hello.d

- name: Verify D compiler with explicit bitness ($DC)
if: ${{ startsWith(inputs.dc, 'dmd') }}
shell: bash
Expand Down
23 changes: 3 additions & 20 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,6 @@ jobs:
with:
dc: ${{ matrix.dc }}

verify-gdmd:
needs: verify-index-js-up-to-date
strategy:
max-parallel: 5
fail-fast: false
matrix:
# gdc and gdmd are only supported on ubuntu
os: [ ubuntu-latest ]
dc: [ gdmd, gdmd-12 ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/verify-d-compiler
with:
dc: ${{ matrix.dc }}

verify-default:
needs: verify-index-js-up-to-date
name: Verify default compiler
Expand All @@ -125,16 +109,16 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macOS-13 ]
dc: [ ldc-latest, dmd-latest, gdc, gdmd-12 ]
dc: [ ldc-latest, dmd-latest, gdc, gdc-12 ]
dub: [ 1.19.0, 1.23.0, latest ]
exclude:
# Excluded because those are actually Linux executables
- { os: windows-latest, dub: 1.19.0 }
# gdc only supports Linux
- { os: windows-latest, dc: gdc }
- { os: windows-latest, dc: gdmd-12 }
- { os: windows-latest, dc: gdc-12 }
- { os: macOS-13, dc: gdc }
- { os: macOS-13, dc: gdmd-12 }
- { os: macOS-13, dc: gdc-12 }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand All @@ -143,7 +127,6 @@ jobs:
with:
compiler: ${{ matrix.dc }}
dub: ${{ matrix.dub }}
gdmd_sha: 'dc0ad9f739795f3ce5c69825efcd5d1d586bb013'

- name: Verify DUB version
run: |
Expand Down
27 changes: 17 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
```


Simply add the setup-dlang action to your GitHub Actions workflow to automatically download and install a D compiler and package manager bundled with the compiler or separately downloaded. The action will automatically add the D binaries to the `PATH` environment variable and set the `DC` environment variable to the selected compiler.
Simply add the setup-dlang action to your GitHub Actions workflow to automatically download and install a D compiler and package manager bundled with the compiler or separately downloaded. The action will automatically add the D binaries to the `PATH` environment variable and set the `DC` and `DMD` environment variables to point to the to the selected compiler and to the dmd wrapper (`gdmd`, `ldmd2`, or `dmd`) of the compiler respectively.
Note, this behavior has been slightly adjusted in v2.
For more details check the changes below.

Expand Down Expand Up @@ -146,22 +146,17 @@ All the ways it can be specified are:
- `ldc-master` - install the latest CI artifacts from https://github.com/ldc-developers/ldc/releases/CI.
This may requires a github api token.

- `gdc` - install the apt package `gdc`
- `gdc-12` - install the apt package `gdc-12`
- `gdmd` - install the apt packages `gdc` and `gdmd`
- `gdmd-12` - install the apt packages `gdc-12` and `gdmd`.
- `gdc` - install the apt package `gdc` and the `gdmd` script
- `gdc-12` - install the apt package `gdc-12` and the `gdmd` script
The available versions of gdc you can install are the versions available in the ubuntu repositories. For `ubuntu-22.04` (`ubuntu-latest` currently) those are `gdc-12`, `gdc-11`, and, `gdc` which corresponds to `gdc-11`. If in doubt check https://packages.ubuntu.com

Whatever compiler you specify you can expect that the environment variable `$DC` will be set to point to that compiler binary.
Additionally `$DMD` will point to the dmd wrapper of the aforementioned compiler if you require a consistent command line interface.
Currently absolute paths are used but you shouldn't depend on it.
The compiler bin folder is also added to `$PATH` so you can run programs like `rdmd` or `dub` without specifying full paths.
Less useful but the library directory of the compiler is also added to `$PATH` on windows and `LD_LIBRARY_PATH` on linux and macos.
The compilers are already configured to embed this path themselves so you really shouldn't have to consider this but know that it is set.

The distinction between `gdc` and `gdmd` is mostly what the `$DC` variable gets set to.
The `gdc` program uses command line arguments that don't match the ones used by `dmd` or `ldc`.
If your project uses these you can specify `gdmd` instead of `gdc` to get a more portable command line interface.

### dub

If you need a specific version of dub or if the D compiler doesn't come with one (`gdc`) you can explicitly install one.
Expand All @@ -179,7 +174,8 @@ Github [generates](https://docs.github.com/en/actions/security-guides/automatic-

### gdmd_sha

In case the `gdmd` script in the ubuntu repositories is too old you can specify a commit sha in https://github.com/D-Programming-GDC/gdmd and this action will use that to download a git version of the script.
The gdmd script is downloaded straight from the [upstream](https://github.com/D-Programming-GDC/gdmd) repository.
You can specify a custom commit sha in case the default one is not appropriate for your use case.

## Compiler support

Expand Down Expand Up @@ -239,3 +235,14 @@ When specifying `dmd-beta` the action may install `dmd-latest` if it determines
Example, if the latest DMD beta is `2.098.1_rc1` and the latest DMD release is `2.099.0` then `dmd-beta` will now resolve to `2.099.0` instead of `2.098.1_rc1`.

The minimum available version of dmd has been raised to `2.065.0`.

## Changes from v2

gdmd is no longer downloaded from the ubuntu repos, it is always pulled in from [D-Programming-GDC/gdmd](https://github.com/D-Programming-GDC/gdmd).

The `gdmd` input to the action has been removed.
Now it is downloaded and setup unconditionally.

Introduces the `DMD` variable which will point to the dmd wrapper of the selected compiler.
For example if you install `ldc-1.37.0` then `DC` will point to `<extracted_path>/ldc2` and `DMD` will point to `<extracted_path>/ldmd2`
This variable is setup for all compilers, automatically.
21 changes: 5 additions & 16 deletions __tests__/action_input.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import * as main from '../src/main'
import * as core from '@actions/core'
import * as d from '../src/d'
import * as testUtils from './test-helpers.test'
testUtils.saveProcessRestorePoint()

describe('Testing compiler when...', function(){
let originalArch: string

beforeAll(function(){
originalArch = process.arch;
jest.spyOn(core, 'getInput').mockReturnValue('')
});
beforeAll(() => jest.spyOn(core, 'getInput').mockReturnValue(''))

test('it is unspecified', () => {
Object.defineProperty(process, 'arch', { value: 'x64' })
Expand Down Expand Up @@ -45,10 +42,6 @@ describe('Testing compiler when...', function(){
mockCompiler('dmd-beta')
expect(main.getActionInputs).toThrow('dmd')
})

afterAll(function(){
Object.defineProperty(process, 'arch', { value: originalArch });
});
});

test('All action inputs', () => {
Expand Down Expand Up @@ -81,15 +74,11 @@ describe('Action messages', () => {
jest.spyOn(d.DMD, 'initialize').mockResolvedValue(<any>nopTool)
jest.spyOn(d.LDC, 'initialize').mockResolvedValue(<any>nopTool)
jest.spyOn(d.GDC, 'initialize').mockResolvedValue(<any>nopTool)
jest.spyOn(d.GDMD, 'initialize').mockResolvedValue(<any>nopTool)
jest.spyOn(d.Dub, 'initialize').mockResolvedValue(<any>nopTool)
// Silence the failures
jest.spyOn(core, 'setFailed').mockImplementation(() => {})
})
beforeEach(() => {
nopTool.makeAvailable.mockClear()
consoleSpy.mockClear()
})
beforeEach(() => jest.clearAllMocks())

function mockInputs(compiler: string, dub: string = '') {
jest.spyOn(core, 'getInput').mockImplementation((key) => {
Expand Down Expand Up @@ -138,7 +127,7 @@ describe('Action messages', () => {

test('Specifying a valid compiler', async () => {
// This is only for coverage's sake
for (var comp of [ 'dmd', 'ldc', 'gdc', 'gdmd' ]) {
for (var comp of [ 'dmd', 'ldc-11.3.0', 'gdc-12' ]) {
mockInputs(comp)
await main.run()
expect(nopTool.makeAvailable).toHaveBeenCalledTimes(1)
Expand Down
8 changes: 6 additions & 2 deletions __tests__/d-compiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ describe('Test Compiler class', () => {
const bin = '/relative/path/to/bin'
const libs = [ '/first', '/second' ]
const name = 'compiler_name'
let c = new Compiler('url', undefined, name, 'ver', bin, libs)
const dmdWrapper = 'dmd_wrapper'
let c = new Compiler('url', undefined, name, 'ver', bin, libs, dmdWrapper)
const root = '/root/folder'

// The values are computed when d.ts is imported so
Expand Down Expand Up @@ -99,6 +100,7 @@ describe('Test Compiler class', () => {
expect(process.env['PATH']).toBe(root + bin + pathSep + '/bin')
expect(process.env['LD_LIBRARY_PATH']).toBe(root + libs[1])
expect(process.env['DC']).toBe(`${root}${bin}${sep}${name}${extension}`)
expect(process.env['DMD']).toBe(`${root}${bin}${sep}${dmdWrapper}${extension}`)
}


Expand All @@ -112,13 +114,15 @@ describe('Test Compiler class', () => {
`${root}${bin}${pathSep}` + '\\bin'
expect(process.env['PATH']).toBe(expPath)
expect(process.env['DC']).toBe(`${root}${bin}${sep}${name}${extension}`)
expect(process.env['DMD']).toBe(`${root}${bin}${sep}${dmdWrapper}${extension}`)
})

test('DC gets set to the absolute path of the compiler', () => {
test('DC and DMD get set to the absolute path of the compiler', () => {
for (const platform of [ 'linux', 'win32', 'darwin', 'freebsd' ]) {
Object.defineProperty(process, 'platform', { value: platform })
c.setDC(root)
expect(process.env['DC']).toBe(root + bin + sep + name + extension)
expect(process.env['DMD']).toBe(root + bin + sep + dmdWrapper + extension)
}
})
})
62 changes: 61 additions & 1 deletion __tests__/d-dmd.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { DMD } from '../src/d'
import * as utils from '../src/utils'
import * as testUtils from './test-helpers.test'
import * as tc from '@actions/tool-cache'
import fs from 'fs'

testUtils.hideConsoleLogs()
testUtils.saveProcessRestorePoint()
testUtils.disableNetwork()
afterEach(() => jest.restoreAllMocks())
function init(version: string) { return DMD.initialize(version, '') }

describe('amd64', () => {
Expand Down Expand Up @@ -432,3 +434,61 @@ test('dmd fails on unsupported platforms', async () => {
Object.defineProperty(process, 'platform', { value: 'freebsd' })
expect(init('dmd-2.109.0')).rejects.toThrow(process.platform)
})


describe('Test makeAvailable', () => {
const root = '/tmp/cache'
const origEnv = process.env

// These values are cached so they match the hosts
const sep = (process.platform == 'win32' ? '\\' : '/')
const exeExt = (process.platform == 'win32' ? '.exe' : '')

beforeEach(() => {
Object.defineProperty(process, 'arch', { value: 'x64' })
jest.spyOn(tc, 'find').mockReturnValue(root)
jest.spyOn(fs, 'existsSync').mockReturnValue(true)
process.env['PATH'] = '/bin'
process.env['LD_LIBRARY_PATH'] = ''
})
afterEach(() => process.env = origEnv)

test('linux', async () => {
Object.defineProperty(process, 'platform', { value: 'linux' })
const dmd = await init('dmd-2.109.1')
await dmd.makeAvailable()
expect(process.env['PATH']).toBe(root + '/dmd2/linux/bin64' + ':/bin')
expect(process.env['LD_LIBRARY_PATH']).toBe(root + '/dmd2/linux/lib64')
expect(process.env['DC']).toBe(root + `/dmd2/linux/bin64${sep}dmd${exeExt}`)
expect(process.env['DMD']).toBe(root + `/dmd2/linux/bin64${sep}dmd${exeExt}`)
})

test('osx', async () => {
Object.defineProperty(process, 'platform', { value: 'darwin' })
const dmd = await init('dmd-2.109.1')
await dmd.makeAvailable()
expect(process.env['PATH']).toBe(root + '/dmd2/osx/bin' + ':/bin')
expect(process.env['LD_LIBRARY_PATH']).toBe(root + '/dmd2/osx/lib')
expect(process.env['DC']).toBe(root + `/dmd2/osx/bin${sep}dmd${exeExt}`)
expect(process.env['DMD']).toBe(root + `/dmd2/osx/bin${sep}dmd${exeExt}`)
})

test('windows', async () => {
Object.defineProperty(process, 'platform', { value: 'win32' })
const dmd = await init('dmd-2.109.1')
await dmd.makeAvailable()

const bits64 = root + '\\dmd2\\windows\\bin64'
const bits32 = root + '\\dmd2\\windows\\bin'
const found64 = process.env['PATH']!.split(':').indexOf(bits64)
const found32 = process.env['PATH']!.split(':').indexOf(bits32)
// check that both folders appear in PATH
expect(found64).toBeGreaterThanOrEqual(0)
expect(found32).toBeGreaterThanOrEqual(0)
// Check that the 64bit folder appears before the 32bit one
expect(found64).toBeLessThan(found32)

expect(process.env['DC']).toBe(root + `\\dmd2\\windows\\bin64${sep}dmd${exeExt}`)
expect(process.env['DMD']).toBe(root + `\\dmd2\\windows\\bin64${sep}dmd${exeExt}`)
})
})
49 changes: 46 additions & 3 deletions __tests__/d-gdc.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { GDC } from '../src/d'
import * as testUtils from './test-helpers.test'
import * as exec from '@actions/exec'
import * as tc from '@actions/tool-cache'

testUtils.saveProcessRestorePoint()
testUtils.hideConsoleLogs()
Expand All @@ -10,11 +11,11 @@ beforeEach(() => {
jest.spyOn(exec, 'exec').mockResolvedValue(0)
Object.defineProperty(process, 'platform', { value: 'linux' })
})
afterEach(() => jest.restoreAllMocks())

async function init (version: string) {
const gdc = await GDC.initialize(version)
await gdc.makeAvailable()
const gdmdSha = 'dc0ad9f739795f3ce5c69825efcd5d1d586bb013'
const gdc = await GDC.initialize(version, gdmdSha)
await gdc.makeAvailableGdc()
}

test('Test simple versions', async () => {
Expand All @@ -36,3 +37,45 @@ test('Test that gdc fails on non-Linux', async () => {
await expect(init('gdc')).rejects.toThrow(platform)
}
})

test('Test gdc invalid versions', async () => {
for (const bad of [ 'gdmd', 'gdc-11.3.0', '11', 'absolute garbage' ])
await expect(init(bad)).rejects.toThrow(bad)
})

test('Test that gdc respects gdmd commit sha', async () => {
jest.spyOn(tc, 'find').mockReturnValue('')
const spy = jest.spyOn(tc, 'downloadTool').mockResolvedValue('')
jest.spyOn(tc, 'cacheFile').mockResolvedValue('/my/path')

const sha = '1a4bcb202d37f0040477444c16bfa69a88e8bec7'
const gdc = await GDC.initialize('gdc', sha)
await gdc.makeAvailableGdmd()

expect(spy.mock.calls[0][0]).toMatch(sha)
})

describe('Gdmd tests', () => {
const cached = '/tmp/cache/gdmd'
beforeEach(() => {
jest.spyOn(tc, 'find').mockReturnValue(cached)
})

test('Test that gdc sets both DC and DMD properly', async () => {
const sha = 'dc0ad9f739795f3ce5c69825efcd5d1d586bb013'
const gdc = await GDC.initialize('gdc', sha)
await gdc.makeAvailable()
expect(process.env['DC']).toBe('/usr/bin/gdc')
expect(process.env['DMD']).toBe('/usr/bin/gdmd')

const gdc_12 = await GDC.initialize('gdc-12', sha)
await gdc_12.makeAvailable()
expect(process.env['DC']).toBe('/usr/bin/gdc-12')
expect(process.env['DMD']).toBe('/usr/bin/gdmd-12')
})

test('Test that gdc gracefully handles invalid gdmd commit shas', async () => {
await expect(GDC.initialize('gdc', '')).rejects.toThrow('gdmd')
})
})

Loading

0 comments on commit ee7eada

Please sign in to comment.