Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fea20a4

Browse files
committed
Metabase Mac App build script / instructions updates
[ci skip]
1 parent 265695c commit fea20a4

5 files changed

Lines changed: 208 additions & 51 deletions

File tree

OSX/Metabase/Metabase-Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
<key>SUEnableAutomaticChecks</key>
4141
<true/>
4242
<key>SUFeedURL</key>
43-
<string>https://s3.amazonaws.com/downloads.metabase.com/appcast.xml</string>
43+
<string>https://downloads.metabase.com/appcast.xml</string>
4444
<key>SUPublicDSAKeyFile</key>
4545
<string>dsa_pub.pem</string>
4646
</dict>

bin/Metabase/Util.pm

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,34 @@ package Metabase::Util;
66
use Cwd 'getcwd';
77
use Exporter;
88
use JSON;
9+
use Readonly;
910
use Term::ANSIColor qw(:constants);
1011

1112
our @ISA = qw(Exporter);
1213
our @EXPORT = qw(config
14+
config_or_die
1315
announce
1416
print_giant_success_banner
1517
get_file_or_die
1618
plist_buddy_exec
1719
OSX_ARTIFACTS_DIR
1820
artifact);
1921

20-
my $config_file = getcwd() . '/bin/config.json';
22+
Readonly my $config_file => getcwd() . '/bin/config.json';
2123
warn "Missing config file: $config_file\n" .
2224
"Please copy $config_file.template, and edit it as needed.\n"
2325
unless (-e $config_file);
24-
my $config = from_json(`cat $config_file`) if -e $config_file;
26+
Readonly my $config => from_json(`cat $config_file`) if -e $config_file;
2527

2628
sub config {
2729
return $config ? $config->{ $_[0] } : '';
2830
}
2931

32+
sub config_or_die {
33+
my ($configKey) = @_;
34+
return config($configKey) or die "Missing config.json property '$configKey'";
35+
}
36+
3037
sub announce {
3138
print "\n\n" . GREEN . $_[0] . RESET . "\n\n";
3239
}

bin/config.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
2-
"codesigningIdentity": "Developer ID Application: Metabase, Inc",
3-
"awsProfile": "metabase",
4-
"awsBucket": "downloads.metabase.com"
2+
"codesigningIdentity": "Developer ID Application: Metabase, Inc",
3+
"appStoreConnectProviderShortName": "BR27ZJK7WW",
4+
"awsProfile": "metabase",
5+
"awsBucket": "downloads.metabase.com",
6+
"cloudFrontDistributionID": "E35CJLWZIZVG7K"
57
}

bin/osx-release

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,19 +95,21 @@ sub build {
9595

9696
# Codesign Metabase.app
9797
sub codesign {
98-
my $codesigning_cert_name = config('codesigningIdentity') or return;
98+
Readonly my $codesigning_cert_name => config_or_die('codesigningIdentity');
9999

100100
announce "Codesigning $app...";
101101

102102
system('codesign', '--force', '--verify',
103103
'--sign', $codesigning_cert_name,
104104
'-r=designated => anchor trusted',
105+
'--timestamp',
106+
'--options', 'runtime',
105107
'--deep', get_file_or_die($app)) == 0 or die "Code signing failed: $!\n";
106108
}
107109

108110
# Verify that Metabase.app was signed correctly
109111
sub verify_codesign {
110-
return unless config('codesigningIdentity');
112+
config_or_die('codesigningIdentity');
111113

112114
announce "Verifying codesigning for $app...";
113115

@@ -154,8 +156,8 @@ sub generate_appcast {
154156

155157
remove_tree($appcast);
156158

157-
my $aws_bucket = config('awsBucket') or return;
158-
my $signature = generate_signature() or return;
159+
Readonly my $aws_bucket => config_or_die('awsBucket');
160+
Readonly my $signature => generate_signature() or die 'Failed to generate appcast signature';
159161

160162
open(my $out, '>', $appcast) or die "Unable to write to $appcast: $!";
161163
print $out Text::Caml->new->render_file(get_file_or_die('bin/templates/appcast.xml.template'), {
@@ -296,15 +298,94 @@ sub create_dmg {
296298
remove_tree($temp_dmg, $dmg_source_dir);
297299
}
298300

301+
# ------------------------------------------------------------ NOTORIZATION ------------------------------------------------------------
302+
303+
sub getAppleID {
304+
return $ENV{'METABASE_MAC_APP_BUILD_APPLE_ID'} or die 'Make sure you export the env var METABASE_MAC_APP_BUILD_APPLE_ID';
305+
}
306+
307+
sub getAscProvider {
308+
return config_or_die('appStoreConnectProviderShortName');
309+
}
310+
311+
sub notarize_file {
312+
my ($filename) = @_;
313+
314+
announce "Notarizing $filename...";
315+
316+
Readonly my $appleID => getAppleID;
317+
Readonly my $ascProvider => getAscProvider;
318+
319+
system('xcrun', 'altool', '--notarize-app',
320+
'--primary-bundle-id', 'com.metabase.Metabase',
321+
'--username', $appleID,
322+
'--password', '@keychain:METABASE_MAC_APP_BUILD_PASSWORD',
323+
'--asc-provider', $ascProvider,
324+
'--file', $filename
325+
) == 0 or die $!;
326+
}
327+
328+
sub wait_for_notarization {
329+
announce "Waiting for notarization...";
330+
331+
Readonly my $appleID => getAppleID;
332+
Readonly my $ascProvider => getAscProvider;
333+
334+
my $status = `xcrun altool --notarization-history 0 -u "$appleID" -p "\@keychain:METABASE_MAC_APP_BUILD_PASSWORD" --asc-provider $ascProvider` or die $!;
335+
336+
print "$status\n";
337+
338+
if ($status =~ m/in progress/) {
339+
print "Notarization is still in progress, waiting a few seconds and trying again...\n";
340+
sleep 5;
341+
wait_for_notarization();
342+
} else {
343+
announce "Notarization successful.";
344+
return "Done";
345+
}
346+
}
347+
348+
sub staple_notorization {
349+
my ($filename) = @_;
350+
351+
announce "Stapling notarization to $filename...";
352+
353+
system('xcrun', 'stapler', 'staple',
354+
'-v', $filename) == 0 or die $1;
355+
356+
announce "Notarization stapled successfully.";
357+
}
358+
359+
# Verify that an app is Signed & Notarized correctly. See https://help.apple.com/xcode/mac/current/#/dev1cc22a95c
360+
sub verify_notarization {
361+
# e.g. /Applications/Metabase.app
362+
my ($appFile) = @_;
363+
364+
announce "Verifying that $appFile is notarized correctly...";
365+
366+
system('spctl', '-a', '-v', $appFile) == 0 or die $!;
367+
368+
announce "Verification successful.";
369+
}
370+
371+
372+
sub notarize_files {
373+
notarize_file(get_file_or_die($zipfile));
374+
notarize_file(get_file_or_die($dmg));
375+
wait_for_notarization();
376+
staple_notorization(get_file_or_die($dmg));
377+
verify_notarization(get_file_or_die($app));
378+
}
379+
299380

300381
# ------------------------------------------------------------ UPLOADING ------------------------------------------------------------
301382

302383

303384
# Upload artifacts to AWS
304385
# Make sure to run `aws configure --profile metabase` first to set up your ~/.aws/config file correctly
305386
sub upload {
306-
my $aws_profile = config('awsProfile') or return;
307-
my $aws_bucket = config('awsBucket') or return;
387+
Readonly my $aws_profile => config_or_die('awsProfile');
388+
Readonly my $aws_bucket => config_or_die('awsBucket');
308389

309390
# Make a folder that contains the files we want to upload
310391
Readonly my $upload_dir => artifact('upload');
@@ -332,6 +413,20 @@ sub upload {
332413
announce "Upload finished."
333414
}
334415

416+
sub create_cloudfront_invalidation {
417+
announce "Creating CloudFront invalidation...";
418+
419+
system ('aws', 'configure',
420+
'set', 'preview.cloudfront', 'true') == 0 or die $!;
421+
422+
system ('aws', 'cloudfront', 'create-invalidation',
423+
'--profile', config_or_die('awsProfile'),
424+
'--distribution-id', config_or_die('cloudFrontDistributionID'),
425+
'--paths', '/appcast.xml') == 0 or die $!;
426+
427+
announce "CloudFront invalidation created successfully.";
428+
}
429+
335430

336431
# ------------------------------------------------------------ RUN ALL ------------------------------------------------------------
337432

@@ -345,7 +440,9 @@ sub all {
345440
generate_appcast;
346441
edit_release_notes;
347442
create_dmg;
443+
notarize_files;
348444
upload;
445+
create_cloudfront_invalidation;
349446
}
350447

351448
# Run all the commands specified in command line args, otherwise default to running 'all'

docs/developers-guide-osx.md

Lines changed: 90 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,64 +3,115 @@
33
NOTE: These instructions are only for packaging a built Metabase uberjar into `Metabase.app`. They are not useful if your goal is to work on Metabase itself; for development, please see
44
our [developers' guide](developers-guide.md).
55

6-
## Prereqs
6+
## First-Time Configuration
77

8-
1. Install XCode.
8+
### Building
99

10-
1. Install XCode command-line tools. In `Xcode` > `Preferences` > `Locations` select your current Xcode version in the `Command Line Tools` drop-down.
10+
The following steps need to be done before building the Mac App:
11+
12+
1. Install XCode.
1113

12-
1. Run `./bin/build` to build the latest version of the uberjar.
14+
1. Add a JRE to the `OSX/Metabase/jre`
1315

14-
1. Next, you'll need to run the following commands before building the app:
16+
You can download a copy of a JRE from https://adoptopenjdk.net/releases.html — make sure you download a JRE rather than JDK. Move the `Contents/Home` directory from the JRE archive into `OSX/Metabase/jre`. For example:
1517

1618
```bash
17-
# Fetch and initialize git submodule
18-
git submodule update --init
19+
wget https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u232-b09/OpenJDK8U-jre_x64_mac_hotspot_8u232b09.tar.gz
20+
tar -xzvf OpenJDK8U-jre_x64_mac_hotspot_8u232b09.tar.gz
21+
mv jdk8u232-b09-jre/Contents/Home/ OSX/Metabase/jre
22+
```
1923

20-
# Install Perl modules used by ./bin/osx-setup and ./bin/osx-release
21-
sudo cpan install File::Copy::Recursive Readonly String::Util Text::Caml JSON
24+
You are fine to use whatever the latest JRE version available is. I have been using the HotSpot JRE instead of the OpenJ9 one but it ultimately shouldn't make a difference.
25+
26+
1. Copy Metabase uberjar to OSX resources dir
2227
23-
# Copy JRE and uberjar
24-
./bin/osx-setup
28+
```bash
29+
cp /path/to/metabase.jar OSX/Resources/metabase.jar
2530
```
31+
32+
Every time you want to build a new version of the Mac App, you can simple update the bundled uberjar the same way.
33+
34+
At this point, you should try opening up the Xcode project and building the Mac App in Xcode by clicking the run button. The app should build and launch at this point. If it doesn't, ask Cam for help!
2635

27-
`./bin/osx-setup` will copy over things like the JRE into the Mac App directory for you. You only need to do this once the first time you plan on building the Mac App.
28-
This also runs `./bin/build` to get the latest uberjar and copies it for you; if the script fails near the end, you can just copy the uberjar to `OSX/Resources/metabase.jar`.)
36+
### Releasing
2937

30-
## Releasing
38+
The following steps are prereqs for releasing the Mac App:
3139

32-
A handy Perl script called `./bin/osx-release` takes care of all of the details for you. Before you run it for the first time, you'll need to set up a few additional things:
3340

34-
```bash
35-
# Install aws command-line client (if needed)
36-
brew install awscli
41+
1. Install XCode command-line tools. In `Xcode` > `Preferences` > `Locations` select your current Xcode version in the `Command Line Tools` drop-down.
3742

38-
# Configure AWS Credentials
39-
# You'll need credentials that give you permission to write the metabase-osx-releases S3 bucket.
40-
# You just need the access key ID and secret key; use the defaults for locale and other options.
41-
aws configure --profile metabase
43+
1. Install CPAN modules
4244

43-
# Obtain a copy of the private key used for signing the app (ask Cam)
44-
# and put a copy of it at ./dsa_priv.pem
45-
cp /path/to/private/key.pem OSX/dsa_priv.pem
46-
```
45+
```bash
46+
sudo cpan
47+
install force File::Copy::Recursive Readonly String::Util Text::Caml JSON
48+
quit
49+
```
50+
51+
You can install [PerlBrew](https://perlbrew.pl/) if you want to install CPAN modules without having to use `sudo`.
4752

48-
You'll need the `Apple Developer ID Application Certificate` in your computer's keychain.
49-
You'll need to generate a Certificate Signing Request from Keychain Access, and have Sameer go to [the Apple Developer Site](https://developer.apple.com/account/mac/certificate/)
50-
and generate one for you, then load the file on your computer.
53+
Normally you shouldn't have to use `install force` to install the modules above, but `File::Copy::Recursive` seems fussy lately and has a failing test that prevents it from installing normally.
5154
52-
Finally, you may need to open the project a single time in Xcode to make sure the appropriate "build schemes" are generated (these are not checked into CI).
53-
Run `open OSX/Metabase.xcodeproj` to open the project, which will automatically generate the appropriate schemes. This only needs to be done once.
55+
1. Install AWS command-line client (if needed)
5456
55-
After that, you are good to go:
56-
```bash
57-
# Build the latest version of the uberjar and copy it to the Mac App build directory
58-
# (You can skip this step if you just ran ./bin/osx-setup, because it does this step for you)
59-
./bin/build && cp target/uberjar/metabase.jar OSX/Resources/metabase.jar
57+
```bash
58+
brew install awscli
59+
```
60+
61+
1. Configure AWS Credentials for `metabase` profile (used to upload artifacts to S3)
6062
61-
# Bundle entire app, and upload to s3
62-
./bin/osx-release
63-
```
63+
You'll need credentials that give you permission to write the metabase-osx-releases S3 bucket.
64+
You just need the access key ID and secret key; use the defaults for locale and other options.
65+
66+
```bash
67+
aws configure --profile metabase
68+
```
69+
70+
1. Obtain a copy of the private key for signing app updates (ask Cam) and put a copy of it at `OSX/dsa_priv.pem`
71+
72+
```bash
73+
cp /path/to/private/key.pem OSX/dsa_priv.pem
74+
```
75+
76+
1. Add `Apple Developer ID Application Certificate` to your computer's keychain.
77+
78+
You'll need to generate a Certificate Signing Request from Keychain Access, and have Sameer go to [the Apple Developer Site](https://developer.apple.com/account/mac/certificate/) and generate one for you, then load the file on your computer.
79+
80+
1. Export your Apple ID for building the app as `METABASE_MAC_APP_BUILD_APPLE_ID`. (This Apple ID must be part of the Metabase org in the Apple developer site. Ask Cam or Sameer to add you if it isn't.)
81+
82+
```bash
83+
# Add this to .zshrc or .bashrc
84+
85+
```
86+
87+
1. Create an App-Specific password for the Apple ID in the previous step
88+
89+
Go to https://appleid.apple.com/account/manage then `Security` > `App-Specific Passwords` > `Generate Password`
90+
91+
1. Store the password in Keychain
92+
93+
```bash
94+
xcrun altool \
95+
--store-password-in-keychain-item "METABASE_MAC_APP_BUILD_PASSWORD" \
96+
-u "$METABASE_MAC_APP_BUILD_APPLE_ID" \
97+
-p <secret_password>
98+
```
99+
100+
## Building & Releasing the Mac App
101+
102+
After following the configuration steps above, to build and release the app you can use the `./bin/osx-release` script:
103+
104+
1. Copy latest uberjar to the Mac App build directory
105+
106+
```bash
107+
cp path/to/metabase.jar OSX/Resources/metabase.jar
108+
```
109+
110+
1. Bundle entire app, and upload to s3
111+
112+
```bash
113+
./bin/osx-release
114+
```
64115
65116
## Debugging ./bin/osx-release
66117

0 commit comments

Comments
 (0)