Package and qualify standalone macOS designer with bundled runtime
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build a macOS app image with a bundled runtime and isolated target tooling."""
|
||||
import argparse, hashlib, json, os, platform, shutil, subprocess, tempfile
|
||||
from pathlib import Path
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
p=argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--java-home', type=Path, required=True)
|
||||
p.add_argument('--destination', type=Path, default=ROOT/'target/distribution')
|
||||
a=p.parse_args()
|
||||
if platform.system()!='Darwin': p.error('This packaging slice qualifies macOS only')
|
||||
java=a.java_home.resolve(); destination=a.destination.resolve()
|
||||
app=destination/'NeitzelFXDesigner.app'
|
||||
for output in (app,destination/'package-manifest.json',destination/'NeitzelFXDesigner-macos.zip'):
|
||||
if output.exists() or output.is_symlink(): p.error(f'Refusing to replace existing output: {output}')
|
||||
env=dict(os.environ, JAVA_HOME=str(java))
|
||||
def run(cmd): subprocess.run(list(map(str,cmd)),cwd=ROOT,env=env,check=True)
|
||||
run([java/'bin/java','-version'])
|
||||
run([os.sys.executable,ROOT/'scripts/check-neitzellib-artifact.py'])
|
||||
run([ROOT/'mvnw','-B','-ntp','-pl','designer-standalone,designer-target-tools','-am','install'])
|
||||
run([ROOT/'mvnw','-B','-ntp','-pl','designer-standalone','org.apache.maven.plugins:maven-dependency-plugin:3.6.1:build-classpath','-DincludeScope=runtime','-Dmdep.outputFile=target/package-classpath.txt'])
|
||||
with tempfile.TemporaryDirectory(prefix='designer-package-') as tmp:
|
||||
stage=Path(tmp)/'input'; stage.mkdir()
|
||||
# Explicit manifest order: the verified Kit patch must precede upstream Kit.
|
||||
paths=[ROOT/'designer-standalone/target/designer-standalone-0.1.0-SNAPSHOT.jar']
|
||||
deps=[Path(s) for s in (ROOT/'designer-standalone/target/package-classpath.txt').read_text().strip().split(os.pathsep)]
|
||||
paths += sorted(deps,key=lambda f:(0 if f.name.startswith('designer-kit-ui-') else 1,f.name))
|
||||
for file in paths:
|
||||
if file.name.endswith('-tests.jar') or any(s in file.name for s in ('junit','intellij','target-tools','preview-api')):
|
||||
raise RuntimeError('Unexpected host dependency: '+str(file))
|
||||
if (stage/file.name).exists(): raise RuntimeError('Duplicate artifact name: '+file.name)
|
||||
shutil.copy2(file,stage/file.name)
|
||||
manifest=Path(tmp)/'MANIFEST.MF'
|
||||
# Fold ASCII artifact names into legal manifest continuation lines.
|
||||
classpath='Class-Path: '+' '.join(f.name for f in paths)
|
||||
lines=[classpath[:70]]
|
||||
classpath=classpath[70:]
|
||||
while classpath:
|
||||
lines.append(' '+classpath[:69]); classpath=classpath[69:]
|
||||
manifest.write_text('Manifest-Version: 1.0\nMain-Class: de.neitzel.fxdesigner.standalone.Launcher\n'+'\n'.join(lines)+'\n\n')
|
||||
empty=Path(tmp)/'empty'; empty.mkdir()
|
||||
run([java/'bin/jar','cfm',stage/'launcher.jar',manifest,'-C',empty,'.'])
|
||||
tools=Path(tmp)/'tools'; tools.mkdir()
|
||||
for name in ('target-tools','contracts','preview-api'):
|
||||
file=ROOT/f'designer-{name}/target/designer-{name}-0.1.0-SNAPSHOT.jar'
|
||||
shutil.copy2(file,tools/file.name)
|
||||
shutil.copy2(ROOT/'THIRD-PARTY-NOTICES.md',stage)
|
||||
runtime=Path(tmp)/'runtime'
|
||||
run([java/'bin/jlink','--add-modules','java.se,jdk.unsupported,jdk.unsupported.desktop,jdk.xml.dom,jdk.crypto.ec,jdk.zipfs,jdk.localedata,jdk.management',
|
||||
'--strip-debug','--no-header-files','--no-man-pages','--output',runtime])
|
||||
destination.mkdir(parents=True,exist_ok=True)
|
||||
run([java/'bin/jpackage','--type','app-image','--name','NeitzelFXDesigner','--app-version','1.0',
|
||||
'--vendor','Neitzel','--input',stage,'--main-jar','launcher.jar','--runtime-image',runtime,
|
||||
'--java-options','--enable-native-access=ALL-UNNAMED','--dest',destination])
|
||||
# jpackage adds every input JAR recursively to the host classpath. Copy worker
|
||||
# artifacts only after it generates the launcher to keep the process boundary.
|
||||
shutil.copytree(tools,app/'Contents/app/tools')
|
||||
cfg=(app/'Contents/app/NeitzelFXDesigner.cfg').read_text()
|
||||
assert '/tools/' not in cfg, 'Target tools leaked into the host launcher'
|
||||
inventory={str(f.relative_to(app)):hashlib.sha256(f.read_bytes()).hexdigest() for f in sorted(app.rglob('*')) if f.is_file()}
|
||||
(destination/'package-manifest.json').write_text(json.dumps({'platform':platform.platform(),'architecture':platform.machine(),
|
||||
'applicationVersion':'0.1.0-SNAPSHOT','bundleVersion':'1.0',
|
||||
'source':subprocess.check_output(['git','rev-parse','HEAD'],cwd=ROOT,text=True).strip(),
|
||||
'workingTreeDirty':bool(subprocess.check_output(['git','status','--porcelain'],cwd=ROOT,text=True).strip()),
|
||||
'javaRelease':(java/'release').read_text(),'files':inventory},indent=2)+'\n')
|
||||
run(['/usr/bin/ditto','-c','-k','--sequesterRsrc','--keepParent',app,destination/'NeitzelFXDesigner-macos.zip'])
|
||||
print(app)
|
||||
Reference in New Issue
Block a user