Skip to content

Commit 9ee6f5e

Browse files
committed
Add build instructions
1 parent 3f5ba13 commit 9ee6f5e

File tree

5 files changed

+468
-0
lines changed

5 files changed

+468
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@
33
The goal of this repository is to host GNU Fortran packages for macOS. These are simple installers, that will install the GCC compilers (including gfortran) in `/usr/local/gfortran`.
44

55
**[Follow this link to download!](https://github.com/fxcoudert/gfortran-for-macOS/releases)**
6+
7+
----
8+
9+
*If you are interested in how these installers are built, the documentation is [here](build_package.md).*

build_package.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# How to build a GCC installer for MacOS
2+
3+
These are my notes on how to build a gfortran / GCC installer for macOS. The goal is to obtain a nice Apple installer, that puts a complete GCC installation in `/usr/local/gfortran`. It then symlinks `gfortran` into `/usr/local/bin`.
4+
5+
The focus is on `gfortran`, as the Fortran community is a heavy user, because Apple's Xcode includes a nice C compiler (`clang`), but no Fortran compiler.
6+
7+
----
8+
9+
To build a package :
10+
11+
- I work in a work directory called `$ROOT`
12+
- I have static GMP, MPFR, MPC and ISL libraries in `$ROOT/deps`. These can be take from the respective Homebrew packages, removing the shared libraries (`*.dylib`).
13+
14+
The steps are the following:
15+
16+
1. Edit `PATH` to make sure I don't have custom software (Homebrew, Anaconda, texlive, `/opt`, etc.)
17+
18+
2. Get the GCC sources (e.g. `gcc-8.2.0.tar.xz`), extract them to `$ROOT/gcc-8.2.0`. Create a `$ROOT/build` and go there.
19+
20+
3. Configure the build:
21+
22+
```
23+
../gcc-8.2.0/configure --prefix=/usr/local/gfortran --with-gmp=$ROOT/deps --enable-languages=c,c++,fortran,objc,obj-c++ --build=x86_64-apple-darwin16
24+
```
25+
26+
On Mojave and later, the following flags need to be added:
27+
28+
```
29+
--disable-multilib --with-native-system-header-dir=/usr/include --with-sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk
30+
```
31+
32+
4. Build by running `make`.
33+
34+
5. Check that the build worked by running `make check-gcc` and `make check-gfortran` inside `$ROOT/build/gcc`. (Or run `make check` at the toplevel, but it needs additional dependencies, such as `autogen`.)
35+
36+
6. Install with: `DESTDIR=$ROOT/package make install`
37+
38+
7. Strip some binaries, remove some hard links:
39+
```
40+
cd $ROOT/package/usr/local/gfortran
41+
rm bin/*-apple-darwin* bin/c++
42+
strip bin/*
43+
strip libexec/gcc/*/*/*1* libexec/gcc/*/*/*2
44+
strip libexec/gcc/*/*/install-tools/fixincl
45+
```
46+
47+
8. Build a signed installer:
48+
```
49+
cd $ROOT
50+
pkgbuild --root package --scripts package_resources --identifier com.gnu.gfortran --version 8.2.0 --install-location / --sign "Developer ID Installer: Francois-Xavier Coudert" gfortran.pkg
51+
```
52+
53+
This requires a `postinstall` script inside `package_resources/`.
54+
55+
9. Verify the signature: `spctl --assess --type install gfortran.pkg`, which should exit with status code 0.
56+
57+
10. Create a DMG for the installer:
58+
```
59+
mkdir gfortran-8.2-Mojave
60+
mv gfortran.pkg gfortran-8.2-Mojave/
61+
cp package-README.html gfortran-8.2-Mojave/README.html
62+
./mkdmg.pl gfortran-8.2-Mojave.dmg gfortran-8.2-Mojave/
63+
```
64+
65+
During that step, look at the `README.html` file and update it if necessary!
66+
67+
11. Cleanup!
68+
```
69+
rm -rf gfortran-8.2-Mojave/
70+
rm -rf package/*/
71+
```

mkdmg.pl

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
#!/usr/bin/perl -w
2+
#
3+
# mkdmg.pl
4+
# Version 1.0
5+
# $Revision: 1.17 $
6+
# $Date: 2004/02/05 02:14:39 $
7+
#
8+
# Copyright 2002 Evan Jones <ejones@uwaterloo.ca>
9+
# http://www.eng.uwaterloo.ca/~ejones/
10+
#
11+
# Released under the BSD Licence, but I would appreciate it if you email
12+
# me if you find this script useful, or if you find any bugs I should fix.
13+
#
14+
# Permission to use, copy, modify, distribute, and sell this software and its
15+
# documentation for any purpose is hereby granted without fee, provided that
16+
# the above copyright notice appear in all copies and that both that
17+
# copyright notice and this permission notice appear in supporting
18+
# documentation. No representations are made about the suitability of this
19+
# software for any purpose. It is provided "as is" without express or
20+
# implied warranty.
21+
#
22+
# Inspired by createDiskImage:
23+
# http://graphics.stepwise.com/Articles/Technical/ProjectBuilder-DMG-Scripts.dmg
24+
#
25+
# Contributions:
26+
# Ben Hines <bhines@alumni.ucsd.edu> - File names with periods and spaces
27+
#
28+
# TODO: Handle spaces in script mode.
29+
30+
use strict;
31+
32+
# Parse command line options
33+
use Getopt::Std;
34+
35+
# TOOL CONFIGURATION
36+
# Customize the tools that are used as you wish
37+
# Note: These are all arrays to avoid undesired shell expansion
38+
39+
# Program that performs copies. You may prefer CpMac.
40+
# If you want to use UFS, you need to run "cp" as root (I suggest using sudo)
41+
my @copy = qw{cp};
42+
my @copyOptions = qw{-R};
43+
44+
# Command to use to expand shell metacharacters: A string because we WANT to use the shell
45+
my $expand = "/bin/ls -d";
46+
sub shellExpandFileName( $ )
47+
{
48+
# expands the parameter using ls and the shell
49+
my $arg = shift();
50+
return split( ' ', `$expand $arg 2> /dev/null` );
51+
}
52+
53+
# Create disk images
54+
my @createImage = qw{hdiutil create};
55+
my @createImageOptions = qw{-megabytes 200 -type SPARSE -fs HFS+ -quiet -volname};
56+
57+
# Mounting disk images
58+
my $mountImage = "hdid";
59+
60+
# Unmounting disk images
61+
my @unmountImage = qw{hdiutil eject};
62+
my @unmountImageOptions = qw{-quiet};
63+
64+
# Converting disk images
65+
my @convertImage = qw{hdiutil convert};
66+
#my @convertImageOptions = qw{-format UDZO -imagekey zlib-level=9 -quiet -o};
67+
my @convertImageOptions = qw{-format UDRO -quiet -o};
68+
69+
# Compressing disk images
70+
my %compressCommands = (
71+
'.dmg.bz2' => [ qw{nice bzip2 --best --force} ],
72+
'.dmg.gz' => [ qw{nice gzip --best --force} ],
73+
'.dmg' => undef,
74+
);
75+
my $compressCommand = undef;
76+
my $compressedImage = undef;
77+
78+
# BEGIN PROGRAM
79+
80+
my %commandLineSwitches;
81+
my $getoptsSucceeded = getopts( 'sv', \%commandLineSwitches );
82+
83+
my $verbose = exists( $commandLineSwitches{v} );
84+
my $scriptMode = exists( $commandLineSwitches{s} );
85+
86+
# Validate command line options
87+
if ( ! $getoptsSucceeded || ( $scriptMode && @ARGV < 1 ) || ( ! $scriptMode && @ARGV < 2 ) )
88+
{
89+
print <<EOF;
90+
usage: mkdmg.pl [OPTION]... <ImageName> [FILE]...
91+
-v Verbose: Prints messages about each step
92+
-s Script mode: Reads source/destination from standard input
93+
EOF
94+
exit( 0 );
95+
}
96+
97+
# Grab the size and image name arguments.
98+
my $readonlyImage = shift();
99+
my $specifiedImage = $readonlyImage;
100+
101+
# Match a compression format based on extension
102+
$compressedImage = undef;
103+
while ( my ($extension, $command) = each %compressCommands )
104+
{
105+
my $re = $extension;
106+
$re =~ s/\./\\./;
107+
if ( $readonlyImage =~ /^(.*)$re$/ )
108+
{
109+
$readonlyImage = $1;
110+
$compressCommand = $compressCommands{$extension};
111+
$compressedImage = "$readonlyImage$extension";
112+
}
113+
}
114+
115+
if ( ! defined $compressedImage )
116+
{
117+
# We must have a "compressed image" name, or else no extension (default compressed image)
118+
fail( "Unable to determine output file type based on the extension: $2" ) if ( $readonlyImage =~ /^(.*)(\.[^.]*)$/ );
119+
120+
$compressCommand = $compressCommands{".dmg.bz2"};
121+
$compressedImage = "$readonlyImage.dmg.bz2";
122+
}
123+
124+
fail( "Zero length file name: Please specify a name without extension" ) if ( length $readonlyImage == 0 );
125+
126+
$readonlyImage .= ".dmg"; # Make sure it ends in .dmg
127+
128+
# Other file names and paths: based on the read-only image name
129+
my $sparseImage = "$readonlyImage.sparseimage";
130+
my $volumeName = $readonlyImage;
131+
$volumeName =~ s/\.dmg$//;
132+
my $mountPath = "/Volumes/$volumeName";
133+
134+
# Don't overwrite temporary files or mount when a volume with the same name is mounted
135+
my @createdFiles = ( $sparseImage, $readonlyImage );
136+
push( @createdFiles, $compressedImage ) if ( defined $compressCommand );
137+
foreach my $file ( @createdFiles, $mountPath )
138+
{
139+
# If the file is specified on the command line, it is okay to clobber it
140+
if ( $file ne $specifiedImage && -e $file )
141+
{
142+
print( "error: $file already exists\n" );
143+
exit( 1 );
144+
}
145+
}
146+
# Since we haven't created any files yet, don't put any on the list
147+
@createdFiles = ();
148+
149+
# Find all the files we are going to copy
150+
my @source;
151+
my @destination;
152+
if ( $scriptMode )
153+
{
154+
# Split input on white space
155+
my @values = ();
156+
while ( <> )
157+
{
158+
push( @values, split( ' ', $_ ) );
159+
}
160+
161+
my $input = 1;
162+
foreach my $value ( @values )
163+
{
164+
if ( $input )
165+
{
166+
push( @source, $value );
167+
$input = 0;
168+
}
169+
else
170+
{
171+
push( @destination, $value );
172+
$input = 1;
173+
}
174+
}
175+
176+
# There is an error if the number of source files does not match the number of destination files
177+
fail( "Uneven number of input lines: A source file does not have a destination" ) if ( @source != @destination );
178+
}
179+
else
180+
{
181+
# For command line mode we do no renaming or shell expansion: Just do it as is
182+
foreach my $line ( @ARGV )
183+
{
184+
push( @source, $line );
185+
push( @destination, "" );
186+
}
187+
}
188+
189+
# Verify that we have input
190+
fail( "No input files specified" ) if ( ! @source );
191+
192+
# Verify that we can find all the source files
193+
foreach my $sourceFile ( @source )
194+
{
195+
my @expandedSource = ( $sourceFile );
196+
# If we are in script mode, expand shell characters
197+
if ( $scriptMode )
198+
{
199+
@expandedSource = shellExpandFileName( $sourceFile );
200+
fail( "Files not found: $sourceFile" ) if ( @expandedSource == 0 );
201+
}
202+
203+
foreach my $file ( @expandedSource )
204+
{
205+
fail( "Cannot not find: $file (matched $sourceFile)" ) if ( ! -e $file );
206+
fail( "Cannot read: $file (matched $sourceFile)" ) if ( ! -r $file );
207+
}
208+
print "$sourceFile matches " . join( ", ", @expandedSource ) . "\n" if ( $verbose );
209+
}
210+
211+
# Create the image and format it
212+
print( "Creating temporary disk image: $sparseImage...\n" ) if ( $verbose );
213+
push( @createdFiles, $sparseImage );
214+
my $code = system( @createImage, $readonlyImage, @createImageOptions, $volumeName );
215+
fail( "Creating temporary disk image failed" ) if ( $code || ! -e $sparseImage );
216+
217+
# Mount the disk image
218+
print( "Mounting temporary disk image: $sparseImage...\n" ) if ( $verbose );
219+
my $output = `$mountImage '$sparseImage'`;
220+
fail( "Disk image not mounted at $mountPath" ) if ( ! -e $mountPath );
221+
222+
fail( "Could not determine mount device" ) if ( $output !~ m{^(/dev/disk\d+)\s+\S+\s*$}m );
223+
my $mountDevice = $1;
224+
225+
# Copy files to the disk image
226+
print( "Copying files...\n" ) if ( $verbose );
227+
228+
$code = 0;
229+
for ( my $i = 0; $i < @source && ! $code; ++ $i )
230+
{
231+
my @expandedSource = ( $source[$i] );
232+
# If we are in script mode, expand shell characters
233+
@expandedSource = shellExpandFileName( $source[$i] ) if ( $scriptMode );
234+
235+
my $destDir = ".";
236+
# If the source expands to multiple items, and a destination is specified,
237+
# the destination is a directory to be created.
238+
if ( @expandedSource > 1 && length $destination[$i] )
239+
{
240+
$destDir = $destination[$i];
241+
}
242+
# If the source is a single file and the destination contains slashes, make sure that each
243+
# directory before the file name is created.
244+
elsif ( $destination[$i] =~ m{(.*)/[^/]*$} )
245+
{
246+
$destDir = $1;
247+
}
248+
249+
if ( ! -e "$mountPath/$destDir" )
250+
{
251+
print( "Creating destination directory: $mountPath/$destDir\n" ) if ( $verbose );
252+
253+
# Split the directory into seperate parts ( dir1/dir2/dir3 => dir1, dir2, dir3 )
254+
my @hierarchy = split( /\//, $destDir );
255+
$destDir = "";
256+
foreach my $subdirectory ( @hierarchy )
257+
{
258+
$destDir .= "/$subdirectory";
259+
mkdir( "$mountPath$destDir" ) or fail( "Could not create directory: $mountPath$destDir: $!" ) if ( ! -e "$mountPath$destDir" );
260+
}
261+
}
262+
263+
$code = system( @copy, @copyOptions, @expandedSource, "$mountPath/$destination[$i]" );
264+
fail( "Files did not copy successfully" ) if ( $code );
265+
}
266+
267+
# Unmount the disk image
268+
print( "Unmounting temporary disk image: $sparseImage...\n" ) if ( $verbose );
269+
$code = system( @unmountImage, $mountDevice, @unmountImageOptions );
270+
fail( "Unmounting disk image failed" ) if ( $code or -e $mountPath );
271+
272+
# Create the read-only image
273+
# Formats (from largest to smallest):
274+
# UDRO - Read only uncompressed
275+
# UDCO - ADC compressed
276+
# UDZO - Zlib compress (specify -imagekey zlib-level=9)
277+
# UDRO.gz - Read only gzip compressed
278+
# UDRO.bz2 - Read only bzip2 compressed (BEST)
279+
print( "Creating read only disk image: $readonlyImage\n" ) if ( $verbose );
280+
push( @createdFiles, $readonlyImage );
281+
system( @convertImage, $sparseImage, @convertImageOptions, $readonlyImage );
282+
unlink( $sparseImage );
283+
unlink( "._$sparseImage" ) if ( -e "._$sparseImage" ); # Remove "resource fork" on any non HFS file systems
284+
fail( "Read only image: $readonlyImage does not exist" ) if ( ! -e $readonlyImage );
285+
unlink( "._$readonlyImage" ) or die "Could not unlink: $!" if ( -e "._$readonlyImage" ); # Remove "resource fork" on any non HFS file systems
286+
287+
print( "Read only disk image sucessfully created: $readonlyImage\n" ) if ( $verbose );
288+
289+
# Compress the disk image
290+
if ( defined $compressCommand )
291+
{
292+
print( "Compressing disk image: $readonlyImage to $compressedImage\n" ) if ( $verbose );
293+
push( @createdFiles, $compressedImage );
294+
$code = system( @$compressCommand, $readonlyImage );
295+
fail( "Compressing image failed" ) if ( $code or ! -e $compressedImage );
296+
297+
print( "Compressed image sucessfully created: $compressedImage\n" ) if ( $verbose );
298+
}
299+
300+
# Cleans up and quits in a gross fashion
301+
sub fail
302+
{
303+
my $string = shift();
304+
print( "error: $string\n" );
305+
306+
# Gross hack: We exploit global variables to try and clean up gracefully
307+
system( @unmountImage, $mountDevice, @unmountImageOptions ) if ( defined $mountDevice );
308+
foreach my $file ( @createdFiles )
309+
{
310+
unlink $file if ( -e $file );
311+
unlink( "._$file" ) if ( -e "._$file" ); # Remove "resource fork" on any non HFS file systems
312+
}
313+
314+
exit( 1 );
315+
}

0 commit comments

Comments
 (0)