Skip to content

Commit b7e0b57

Browse files
authored
feat: support for macOS-14 and arm64 architecture (#46)
2 parents fd20646 + 2315d55 commit b7e0b57

File tree

2 files changed

+162
-26
lines changed

2 files changed

+162
-26
lines changed

.github/workflows/test-workflow.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
name: Test
22

3-
on: [push]
3+
on: [push, pull_request]
44

55
jobs:
66
Test:
77
runs-on: ${{ matrix.os }}
88
strategy:
99
fail-fast: false
1010
matrix:
11-
os: [ubuntu-latest, macos-13, windows-latest]
12-
fpm-version: ["v0.8.0", "v0.9.0", "latest"]
11+
os: [ubuntu-latest, macos-13, macos-14, windows-latest]
12+
fpm-version: ["v0.8.0", "v0.9.0", "v0.10.1", "latest"]
1313
node-version: ["20.x"]
14+
exclude:
15+
# v0.8.0 install.sh is broken, can't be built from source on macOS ARM64
16+
- os: macos-14
17+
fpm-version: "v0.8.0"
1418

1519
steps:
1620
- name: Checkout
@@ -24,7 +28,7 @@ jobs:
2428
- name: MacOS system setup
2529
if: runner.os == 'macOS'
2630
run: |
27-
brew install gcc@10
31+
brew install gcc@13
2832
2933
- name: Install dependencies
3034
run: npm ci

index.js

Lines changed: 154 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const io = require('@actions/io');
44
const exec = require('@actions/exec');
55
const path = require('path');
66
const github = require('@actions/github');
7+
const os = require('os');
78

89
// Main entry function
910
//
@@ -39,32 +40,36 @@ async function main(){
3940

4041
}
4142

43+
// Detect architecture
44+
const arch = os.arch();
45+
console.log(`System architecture: ${arch}`);
46+
console.log(`This platform is ${process.platform}`);
47+
4248
// Build download path
4349
const fetchPath = fpmRepo + '/releases/download/' + fpmVersion + '/';
44-
const filename = getFPMFilename(fpmVersion,process.platform);
50+
const filename = getFPMFilename(fpmVersion, process.platform, arch);
4551

46-
console.log(`This platform is ${process.platform}`);
4752
console.log(`Fetching fpm from ${fetchPath}${filename}`);
4853

4954
// Download release
5055
var fpmPath;
5156
try {
52-
53-
// Try downloading the file without the compiler suffix
54-
const filename = getFPMFilename(fpmVersion, process.platform);
57+
58+
// Try downloading the file without the compiler suffix
59+
const filename = getFPMFilename(fpmVersion, process.platform, arch);
5560
fpmPath = await tc.downloadTool(fetchPath + filename);
5661

5762
} catch (error) {
58-
63+
5964
// If download fails, try adding compiler suffixes
6065
const compilers = ['gcc-10', 'gcc-11', 'gcc-12', 'gcc-13', 'gcc-14'];
61-
66+
6267
let success = false;
63-
68+
6469
for (const compiler of compilers) {
65-
70+
6671
// Generate the filename with the compiler suffix
67-
const filenameWithSuffix = getFPMFilename(fpmVersion, process.platform, compiler);
72+
const filenameWithSuffix = getFPMFilename(fpmVersion, process.platform, arch, compiler);
6873
console.log(`Trying to fetch compiler-built fpm: ${filenameWithSuffix}`);
6974

7075
try {
@@ -74,10 +79,32 @@ async function main(){
7479
} catch (error) {
7580
console.log(` -> Failed to download ${filenameWithSuffix}`);
7681
}
77-
82+
7883
}
7984

8085
if (!success) {
86+
// On macOS ARM64, fall back to building from source
87+
if (process.platform === 'darwin' && arch === 'arm64') {
88+
console.log('No pre-built ARM64 binary found, falling back to building from source');
89+
90+
// For older versions without working install.sh, we can't build from source
91+
const versionNum = fpmVersion.replace('v', '');
92+
const versionParts = versionNum.split('.').map(Number);
93+
const isOldVersion = versionParts[0] === 0 && versionParts[1] < 9;
94+
95+
if (isOldVersion) {
96+
core.setFailed(
97+
`Building fpm ${fpmVersion} from source is not supported on macOS ARM64.\n` +
98+
'Please use fpm v0.9.0 or later, which has a working install.sh script.\n' +
99+
'For example, set fpm-version to "v0.9.0", "v0.10.1" or "latest".'
100+
);
101+
return;
102+
}
103+
104+
await installFromSource(fpmVersion, fpmRepo);
105+
return;
106+
}
107+
81108
core.setFailed(`Error while trying to fetch fpm - please check that a version exists at the above release url.`);
82109
}
83110
}
@@ -120,31 +147,39 @@ async function main(){
120147
//
121148
// <version> is a string of form X.Y.Z corresponding to a release of fpm
122149
// <os> is either 'linux', 'macos', or 'windows'
123-
// <arch> here is always 'x86_64'
150+
// <arch> is 'x86_64' or 'arm64'
124151
// <compiler> is an optional string like '-gcc-12'
125152
//
126-
function getFPMFilename(fpmVersion, platform, compiler = '') {
153+
function getFPMFilename(fpmVersion, platform, arch, compiler = '') {
127154
var filename = 'fpm-';
128-
155+
129156
// Remove the leading 'v' if it exists
130157
filename += fpmVersion.replace('v', '') + '-';
131158

159+
// Map Node.js arch to FPM arch naming
160+
let fpmArch = 'x86_64';
161+
if (arch === 'arm64') {
162+
fpmArch = 'arm64';
163+
} else if (arch === 'x64') {
164+
fpmArch = 'x86_64';
165+
}
166+
132167
// Add the platform and architecture
133168
if (platform === 'linux') {
134-
filename += 'linux-x86_64';
169+
filename += `linux-${fpmArch}`;
135170
} else if (platform === 'darwin') {
136-
filename += 'macos-x86_64';
171+
filename += `macos-${fpmArch}`;
137172
} else if (platform === 'win32') {
138-
filename += 'windows-x86_64';
173+
filename += `windows-${fpmArch}`;
139174
} else {
140175
core.setFailed('Unknown platform');
141176
}
142177

143-
// If a compiler is provided, append it as a suffix
144-
if (compiler) filename += `-${compiler}`;
145-
146-
// Add the '.exe' suffix for Windows
147-
if (platform === 'win32') filename += '.exe';
178+
// If a compiler is provided, append it as a suffix
179+
if (compiler) filename += `-${compiler}`;
180+
181+
// Add the '.exe' suffix for Windows
182+
if (platform === 'win32') filename += '.exe';
148183

149184
return filename;
150185
}
@@ -165,5 +200,102 @@ async function getLatestReleaseVersion(token){
165200
}
166201

167202

203+
// Install fpm from source using the install.sh script
204+
// This is used on macOS ARM64 where pre-built binaries are not available
205+
//
206+
async function installFromSource(fpmVersion, fpmRepo){
207+
208+
try {
209+
210+
// Find gfortran - it may be versioned (e.g., gfortran-13)
211+
// Use gcc <= 13 for compatibility with older fpm versions
212+
let gfortranCmd = 'gfortran';
213+
let foundGfortran = false;
214+
215+
try {
216+
await exec.exec('which', ['gfortran'], { silent: true });
217+
foundGfortran = true;
218+
} catch (error) {
219+
// gfortran not found, try versioned
220+
}
221+
222+
// If we found unversioned gfortran, check if it's gcc >= 14
223+
// If so, or if we didn't find unversioned gfortran, look for a versioned one <= 13
224+
// Always prefer versioned <= 13 for compatibility
225+
let foundVersioned = false;
226+
for (const ver of [13, 12, 11, 10]) {
227+
try {
228+
await exec.exec('which', [`gfortran-${ver}`], { silent: true });
229+
gfortranCmd = `gfortran-${ver}`;
230+
foundVersioned = true;
231+
console.log(`Found ${gfortranCmd}`);
232+
break;
233+
} catch (e) {
234+
// Continue searching
235+
}
236+
}
237+
238+
if (!foundVersioned && !foundGfortran) {
239+
core.setFailed(
240+
'gfortran is required to build fpm from source on macOS ARM64.\n' +
241+
'Please install gcc version 10-13 before running this action, for example:\n' +
242+
' - name: Install gfortran\n' +
243+
' run: brew install gcc@13\n' +
244+
'Or use fortran-lang/setup-fortran to install a Fortran compiler.'
245+
);
246+
return;
247+
}
248+
249+
const versionNumber = fpmVersion.replace('v', '');
250+
251+
// Download the full source tarball for the requested version
252+
const tarballUrl = `${fpmRepo}/archive/refs/tags/${fpmVersion}.tar.gz`;
253+
console.log(`Downloading fpm source from: ${tarballUrl}`);
254+
const tarballPath = await tc.downloadTool(tarballUrl);
255+
256+
// Create a temporary directory for building
257+
const buildDir = path.join(process.env.RUNNER_TEMP || '/tmp', 'fpm-build');
258+
await io.mkdirP(buildDir);
259+
260+
// Extract the tarball
261+
const extractDir = await tc.extractTar(tarballPath, buildDir);
262+
console.log(`Extracted to: ${extractDir}`);
263+
264+
// The extracted directory will be named fpm-<version> (without the 'v')
265+
const fpmSourceDir = path.join(buildDir, `fpm-${versionNumber}`);
266+
267+
console.log('Installing fpm from source using install.sh...');
268+
269+
// Create installation directory
270+
const installPrefix = path.join(process.env.HOME || process.env.USERPROFILE || '/tmp', '.local');
271+
const installDir = path.join(installPrefix, 'bin');
272+
await io.mkdirP(installDir);
273+
274+
// Run the install.sh script with FC environment variable
275+
const installScript = path.join(fpmSourceDir, 'install.sh');
276+
await exec.exec('bash', [installScript, `--prefix=${installPrefix}`], {
277+
cwd: fpmSourceDir,
278+
env: {
279+
...process.env,
280+
FC: gfortranCmd
281+
}
282+
});
283+
284+
// Add to path
285+
core.addPath(installDir);
286+
console.log(`fpm installed and added to path at ${installDir}`);
287+
288+
// Clean up build directory
289+
await io.rmRF(buildDir);
290+
291+
} catch (error) {
292+
293+
core.setFailed(`Failed to install fpm from source: ${error.message}`);
294+
295+
}
296+
297+
}
298+
299+
168300
// Call entry function
169301
main();

0 commit comments

Comments
 (0)